Merge with master
This commit is contained in:
@@ -8,12 +8,12 @@ ClassVars:
|
|||||||
Enabled: false
|
Enabled: false
|
||||||
LineLength:
|
LineLength:
|
||||||
Max: 120
|
Max: 120
|
||||||
|
Lint/UriEscapeUnescape:
|
||||||
|
Enabled: false
|
||||||
MethodLength:
|
MethodLength:
|
||||||
Max: 20
|
Max: 20
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'app/controllers/enumeration/cli_options.rb'
|
- 'app/controllers/enumeration/cli_options.rb'
|
||||||
Lint/UriEscapeUnescape:
|
|
||||||
Enabled: false
|
|
||||||
Metrics/AbcSize:
|
Metrics/AbcSize:
|
||||||
Max: 25
|
Max: 25
|
||||||
Metrics/BlockLength:
|
Metrics/BlockLength:
|
||||||
@@ -29,3 +29,6 @@ Style/Documentation:
|
|||||||
Enabled: false
|
Enabled: false
|
||||||
Style/FormatStringToken:
|
Style/FormatStringToken:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
Style/NumericPredicate:
|
||||||
|
Exclude:
|
||||||
|
- 'app/controllers/vuln_api.rb'
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'controllers/core'
|
require_relative 'controllers/core'
|
||||||
|
require_relative 'controllers/vuln_api'
|
||||||
require_relative 'controllers/custom_directories'
|
require_relative 'controllers/custom_directories'
|
||||||
require_relative 'controllers/wp_version'
|
require_relative 'controllers/wp_version'
|
||||||
require_relative 'controllers/main_theme'
|
require_relative 'controllers/main_theme'
|
||||||
|
|||||||
30
app/controllers/vuln_api.rb
Normal file
30
app/controllers/vuln_api.rb
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module WPScan
|
||||||
|
module Controller
|
||||||
|
# Controller to handle the API token
|
||||||
|
class VulnApi < CMSScanner::Controller::Base
|
||||||
|
def cli_options
|
||||||
|
[
|
||||||
|
OptString.new(['--api-token TOKEN', 'The WPVulnDB API Token to display vulnerability data'])
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
def before_scan
|
||||||
|
return unless ParsedCli.api_token
|
||||||
|
|
||||||
|
DB::VulnApi.token = ParsedCli.api_token
|
||||||
|
|
||||||
|
api_status = DB::VulnApi.status
|
||||||
|
|
||||||
|
raise Error::InvalidApiToken if api_status['error']
|
||||||
|
raise Error::ApiLimitReached if api_status['requests_remaining'] == 0
|
||||||
|
raise api_status['http_error'] if api_status['http_error']
|
||||||
|
end
|
||||||
|
|
||||||
|
def after_scan
|
||||||
|
output('status', status: DB::VulnApi.status, api_requests: WPScan.api_requests)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -15,9 +15,16 @@ module WPScan
|
|||||||
@uri = Addressable::URI.parse(blog.url(path_from_blog))
|
@uri = Addressable::URI.parse(blog.url(path_from_blog))
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ JSON ]
|
# Retrieve the metadata from the vuln API if available (and a valid token is given),
|
||||||
|
# or the local metadata db otherwise
|
||||||
|
# @return [ Hash ]
|
||||||
|
def metadata
|
||||||
|
@metadata ||= db_data.empty? ? DB::Plugin.metadata_at(slug) : db_data
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Hash ]
|
||||||
def db_data
|
def db_data
|
||||||
@db_data ||= DB::Plugin.db_data(slug)
|
@db_data ||= DB::VulnApi.plugin_data(slug)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
|
|||||||
@@ -21,9 +21,16 @@ module WPScan
|
|||||||
parse_style
|
parse_style
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Retrieve the metadata from the vuln API if available (and a valid token is given),
|
||||||
|
# or the local metadata db otherwise
|
||||||
# @return [ JSON ]
|
# @return [ JSON ]
|
||||||
|
def metadata
|
||||||
|
@metadata ||= db_data.empty? ? DB::Theme.metadata_at(slug) : db_data
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Hash ]
|
||||||
def db_data
|
def db_data
|
||||||
@db_data ||= DB::Theme.db_data(slug)
|
@db_data ||= DB::VulnApi.theme_data(slug)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
|
|||||||
@@ -60,18 +60,18 @@ module WPScan
|
|||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def latest_version
|
def latest_version
|
||||||
@latest_version ||= db_data['latest_version'] ? Model::Version.new(db_data['latest_version']) : nil
|
@latest_version ||= metadata['latest_version'] ? Model::Version.new(metadata['latest_version']) : nil
|
||||||
end
|
end
|
||||||
|
|
||||||
# Not used anywhere ATM
|
# Not used anywhere ATM
|
||||||
# @return [ Boolean ]
|
# @return [ Boolean ]
|
||||||
def popular?
|
def popular?
|
||||||
@popular ||= db_data['popular']
|
@popular ||= metadata['popular'] ? true : false
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def last_updated
|
def last_updated
|
||||||
@last_updated ||= db_data['last_updated']
|
@last_updated ||= metadata['last_updated']
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Boolean ]
|
# @return [ Boolean ]
|
||||||
|
|||||||
@@ -35,9 +35,16 @@ module WPScan
|
|||||||
@all_numbers.sort! { |a, b| Gem::Version.new(b) <=> Gem::Version.new(a) }
|
@all_numbers.sort! { |a, b| Gem::Version.new(b) <=> Gem::Version.new(a) }
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ JSON ]
|
# Retrieve the metadata from the vuln API if available (and a valid token is given),
|
||||||
|
# or the local metadata db otherwise
|
||||||
|
# @return [ Hash ]
|
||||||
|
def metadata
|
||||||
|
@metadata ||= db_data.empty? ? DB::Version.metadata_at(number) : db_data
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Hash ]
|
||||||
def db_data
|
def db_data
|
||||||
@db_data ||= DB::Version.db_data(number)
|
@db_data ||= DB::VulnApi.wordpress_data(number)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Array<Vulnerability> ]
|
# @return [ Array<Vulnerability> ]
|
||||||
@@ -55,12 +62,12 @@ module WPScan
|
|||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def release_date
|
def release_date
|
||||||
@release_date ||= db_data['release_date'] || 'Unknown'
|
@release_date ||= metadata['release_date'] || 'Unknown'
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def status
|
def status
|
||||||
@status ||= db_data['status'] || 'Unknown'
|
@status ||= metadata['status'] || 'Unknown'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
13
app/views/cli/vuln_api/status.erb
Normal file
13
app/views/cli/vuln_api/status.erb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<% unless @status.empty? -%>
|
||||||
|
<% if @status['http_error'] -%>
|
||||||
|
<%= critical_icon %> WPVulnDB API, <%= @status['http_error'].to_s %>
|
||||||
|
<% else -%>
|
||||||
|
<%= info_icon %> WPVulnDB API OK
|
||||||
|
| Plan: <%= @status['plan'] %>
|
||||||
|
| Requests Done (during the scan): <%= @api_requests %>
|
||||||
|
| Requests Remaining: <%= @status['requests_remaining'] %>
|
||||||
|
<% end -%>
|
||||||
|
<% else -%>
|
||||||
|
<%= warning_icon %> No WPVulnDB API Token given, as a result vulnerability data has not been output.
|
||||||
|
<%= warning_icon %> You can get a free API token with 50 daily requests by registering at https://wpvulndb.com/register.
|
||||||
|
<% end -%>
|
||||||
13
app/views/json/vuln_api/status.erb
Normal file
13
app/views/json/vuln_api/status.erb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
"vuln_api": {
|
||||||
|
<% unless @status.empty? -%>
|
||||||
|
<% if @status['http_error'] -%>
|
||||||
|
"http_error": <%= @status['http_error'].to_s.to_json %>
|
||||||
|
<% else -%>
|
||||||
|
"plan": <%= @status['plan'].to_json %>,
|
||||||
|
"requests_done_during_scan": <%= @api_requests.to_json %>,
|
||||||
|
"requests_remaining": <%= @status['requests_remaining'].to_json %>
|
||||||
|
<% end -%>
|
||||||
|
<% else -%>
|
||||||
|
"error": "No WPVulnDB API Token given, as a result vulnerability data has not been output.\nYou can get a free API token with 50 daily requests by registering at https://wpvulndb.com/register."
|
||||||
|
<% end -%>
|
||||||
|
},
|
||||||
@@ -5,6 +5,7 @@ require 'wpscan'
|
|||||||
|
|
||||||
WPScan::Scan.new do |s|
|
WPScan::Scan.new do |s|
|
||||||
s.controllers <<
|
s.controllers <<
|
||||||
|
WPScan::Controller::VulnApi.new <<
|
||||||
WPScan::Controller::CustomDirectories.new <<
|
WPScan::Controller::CustomDirectories.new <<
|
||||||
WPScan::Controller::InterestingFindings.new <<
|
WPScan::Controller::InterestingFindings.new <<
|
||||||
WPScan::Controller::WpVersion.new <<
|
WPScan::Controller::WpVersion.new <<
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ require 'wpscan'
|
|||||||
report = MemoryProfiler.report(top: 15) do
|
report = MemoryProfiler.report(top: 15) do
|
||||||
WPScan::Scan.new do |s|
|
WPScan::Scan.new do |s|
|
||||||
s.controllers <<
|
s.controllers <<
|
||||||
|
WPScan::Controller::VulnApi.new <<
|
||||||
WPScan::Controller::CustomDirectories.new <<
|
WPScan::Controller::CustomDirectories.new <<
|
||||||
WPScan::Controller::InterestingFindings.new <<
|
WPScan::Controller::InterestingFindings.new <<
|
||||||
WPScan::Controller::WpVersion.new <<
|
WPScan::Controller::WpVersion.new <<
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ StackProf.run(mode: :cpu, out: '/tmp/stackprof-cpu.dump', interval: 500) do
|
|||||||
# require_relative 'wpscan' doesn't work
|
# require_relative 'wpscan' doesn't work
|
||||||
WPScan::Scan.new do |s|
|
WPScan::Scan.new do |s|
|
||||||
s.controllers <<
|
s.controllers <<
|
||||||
|
WPScan::Controller::VulnApi.new <<
|
||||||
WPScan::Controller::CustomDirectories.new <<
|
WPScan::Controller::CustomDirectories.new <<
|
||||||
WPScan::Controller::InterestingFindings.new <<
|
WPScan::Controller::InterestingFindings.new <<
|
||||||
WPScan::Controller::WpVersion.new <<
|
WPScan::Controller::WpVersion.new <<
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ require 'uri'
|
|||||||
require 'time'
|
require 'time'
|
||||||
require 'readline'
|
require 'readline'
|
||||||
require 'securerandom'
|
require 'securerandom'
|
||||||
|
# Monkey Patches/Fixes/Override
|
||||||
|
require 'wpscan/typhoeus/response' # Adds a from_vuln_api? method
|
||||||
# Custom Libs
|
# Custom Libs
|
||||||
require 'wpscan/helper'
|
require 'wpscan/helper'
|
||||||
require 'wpscan/db'
|
require 'wpscan/db'
|
||||||
@@ -38,12 +39,28 @@ module WPScan
|
|||||||
APP_DIR = Pathname.new(__FILE__).dirname.join('..', 'app').expand_path
|
APP_DIR = Pathname.new(__FILE__).dirname.join('..', 'app').expand_path
|
||||||
DB_DIR = Pathname.new(Dir.home).join('.wpscan', 'db')
|
DB_DIR = Pathname.new(Dir.home).join('.wpscan', 'db')
|
||||||
|
|
||||||
|
Typhoeus.on_complete do |response|
|
||||||
|
next if response.cached? || !response.from_vuln_api?
|
||||||
|
|
||||||
|
self.api_requests += 1
|
||||||
|
end
|
||||||
|
|
||||||
# Override, otherwise it would be returned as 'wp_scan'
|
# Override, otherwise it would be returned as 'wp_scan'
|
||||||
#
|
#
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def self.app_name
|
def self.app_name
|
||||||
'wpscan'
|
'wpscan'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @return [ Integer ]
|
||||||
|
def self.api_requests
|
||||||
|
@@api_requests ||= 0
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param [ Integer ] value
|
||||||
|
def self.api_requests=(value)
|
||||||
|
@@api_requests = value
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
require "#{WPScan::APP_DIR}/app"
|
require "#{WPScan::APP_DIR}/app"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ module WPScan
|
|||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def default_user_agent
|
def default_user_agent
|
||||||
"WPScan v#{VERSION} (https://wpscan.org/)"
|
@default_user_agent ||= "WPScan v#{VERSION} (https://wpscan.org/)"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ require_relative 'db/theme'
|
|||||||
require_relative 'db/wp_version'
|
require_relative 'db/wp_version'
|
||||||
require_relative 'db/fingerprints'
|
require_relative 'db/fingerprints'
|
||||||
|
|
||||||
|
require_relative 'db/vuln_api'
|
||||||
|
|
||||||
require_relative 'db/dynamic_finders/base'
|
require_relative 'db/dynamic_finders/base'
|
||||||
require_relative 'db/dynamic_finders/plugin'
|
require_relative 'db/dynamic_finders/plugin'
|
||||||
require_relative 'db/dynamic_finders/theme'
|
require_relative 'db/dynamic_finders/theme'
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ module WPScan
|
|||||||
module DB
|
module DB
|
||||||
# Plugin DB
|
# Plugin DB
|
||||||
class Plugin < WpItem
|
class Plugin < WpItem
|
||||||
# @return [ String ]
|
# @return [ Hash ]
|
||||||
def self.db_file
|
def self.metadata
|
||||||
@db_file ||= DB_DIR.join('plugins.json').to_s
|
@metadata ||= super['plugins'] || {}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ module WPScan
|
|||||||
# WP Plugins
|
# WP Plugins
|
||||||
class Plugins < WpItems
|
class Plugins < WpItems
|
||||||
# @return [ JSON ]
|
# @return [ JSON ]
|
||||||
def self.db
|
def self.metadata
|
||||||
Plugin.db
|
Plugin.metadata
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ module WPScan
|
|||||||
module DB
|
module DB
|
||||||
# Theme DB
|
# Theme DB
|
||||||
class Theme < WpItem
|
class Theme < WpItem
|
||||||
# @return [ String ]
|
# @return [ Hash ]
|
||||||
def self.db_file
|
def self.metadata
|
||||||
@db_file ||= DB_DIR.join('themes.json').to_s
|
@metadata ||= super['themes'] || {}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ module WPScan
|
|||||||
# WP Themes
|
# WP Themes
|
||||||
class Themes < WpItems
|
class Themes < WpItems
|
||||||
# @return [ JSON ]
|
# @return [ JSON ]
|
||||||
def self.db
|
def self.metadata
|
||||||
Theme.db
|
Theme.metadata
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ module WPScan
|
|||||||
class Updater
|
class Updater
|
||||||
# /!\ Might want to also update the Enumeration#cli_options when some filenames are changed here
|
# /!\ Might want to also update the Enumeration#cli_options when some filenames are changed here
|
||||||
FILES = %w[
|
FILES = %w[
|
||||||
plugins.json themes.json wordpresses.json
|
metadata.json wp_fingerprints.json
|
||||||
timthumbs-v3.txt config_backups.txt db_exports.txt
|
timthumbs-v3.txt config_backups.txt db_exports.txt
|
||||||
dynamic_finders.yml wp_fingerprints.json LICENSE
|
dynamic_finders.yml LICENSE
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
OLD_FILES = %w[wordpress.db user-agents.txt dynamic_finders_01.yml].freeze
|
OLD_FILES = %w[
|
||||||
|
wordpress.db user-agents.txt dynamic_finders_01.yml
|
||||||
|
wordpresses.json plugins.json themes.json
|
||||||
|
].freeze
|
||||||
|
|
||||||
attr_reader :repo_directory
|
attr_reader :repo_directory
|
||||||
|
|
||||||
|
|||||||
78
lib/wpscan/db/vuln_api.rb
Normal file
78
lib/wpscan/db/vuln_api.rb
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module WPScan
|
||||||
|
module DB
|
||||||
|
# WPVulnDB API
|
||||||
|
class VulnApi
|
||||||
|
NON_ERROR_CODES = [200, 401, 404].freeze
|
||||||
|
|
||||||
|
class << self
|
||||||
|
attr_accessor :token
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Addressable::URI ]
|
||||||
|
def self.uri
|
||||||
|
@uri ||= Addressable::URI.parse('https://wpvulndb.com/api/v3/')
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param [ String ] path
|
||||||
|
# @param [ Hash ] params
|
||||||
|
#
|
||||||
|
# @return [ Hash ]
|
||||||
|
def self.get(path, params = {})
|
||||||
|
return {} unless token
|
||||||
|
|
||||||
|
res = Browser.get(uri.join(path), params.merge(request_params))
|
||||||
|
|
||||||
|
return JSON.parse(res.body) if NON_ERROR_CODES.include?(res.code)
|
||||||
|
|
||||||
|
raise Error::HTTP, res
|
||||||
|
rescue Error::HTTP => e
|
||||||
|
retries ||= 0
|
||||||
|
|
||||||
|
if (retries += 1) <= 3
|
||||||
|
sleep(1)
|
||||||
|
retry
|
||||||
|
end
|
||||||
|
|
||||||
|
{ 'http_error' => e }
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Hash ]
|
||||||
|
def self.plugin_data(slug)
|
||||||
|
get("plugins/#{slug}")&.dig(slug) || {}
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Hash ]
|
||||||
|
def self.theme_data(slug)
|
||||||
|
get("themes/#{slug}")&.dig(slug) || {}
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Hash ]
|
||||||
|
def self.wordpress_data(version_number)
|
||||||
|
get("wordpresses/#{version_number.tr('.', '')}")&.dig(version_number) || {}
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Hash ]
|
||||||
|
def self.status
|
||||||
|
json = get('status', params: { version: WPScan::VERSION }, cache_ttl: 0)
|
||||||
|
|
||||||
|
json['requests_remaining'] = 'Unlimited' if json['requests_remaining'] == -1
|
||||||
|
|
||||||
|
json
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Hash ]
|
||||||
|
def self.request_params
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Host' => uri.host, # Reset in case user provided a --vhost for the target
|
||||||
|
'Referer' => nil, # Removes referer set by the cmsscanner to the target url
|
||||||
|
'User-Agent' => Browser.instance.default_user_agent,
|
||||||
|
'Authorization' => "Token token=#{token}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -6,14 +6,19 @@ module WPScan
|
|||||||
class WpItem
|
class WpItem
|
||||||
# @param [ String ] identifier The plugin/theme slug or version number
|
# @param [ String ] identifier The plugin/theme slug or version number
|
||||||
#
|
#
|
||||||
# @return [ Hash ] The JSON data from the DB associated to the identifier
|
# @return [ Hash ] The JSON data from the metadata associated to the identifier
|
||||||
def self.db_data(identifier)
|
def self.metadata_at(identifier)
|
||||||
db[identifier] || {}
|
metadata[identifier] || {}
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ JSON ]
|
# @return [ JSON ]
|
||||||
def self.db
|
def self.metadata
|
||||||
@db ||= read_json_file(db_file)
|
@metadata ||= read_json_file(metadata_file)
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ String ]
|
||||||
|
def self.metadata_file
|
||||||
|
@metadata_file ||= DB_DIR.join('metadata.json').to_s
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,17 +6,17 @@ module WPScan
|
|||||||
class WpItems
|
class WpItems
|
||||||
# @return [ Array<String> ] The slug of all items
|
# @return [ Array<String> ] The slug of all items
|
||||||
def self.all_slugs
|
def self.all_slugs
|
||||||
db.keys
|
metadata.keys
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Array<String> ] The slug of all popular items
|
# @return [ Array<String> ] The slug of all popular items
|
||||||
def self.popular_slugs
|
def self.popular_slugs
|
||||||
db.select { |_key, item| item['popular'] == true }.keys
|
metadata.select { |_key, item| item['popular'] == true }.keys
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Array<String> ] The slug of all vulnerable items
|
# @return [ Array<String> ] The slug of all vulnerable items
|
||||||
def self.vulnerable_slugs
|
def self.vulnerable_slugs
|
||||||
db.reject { |_key, item| item['vulnerabilities'].empty? }.keys
|
metadata.select { |_key, item| item['vulnerabilities'] == true }.keys
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ module WPScan
|
|||||||
module DB
|
module DB
|
||||||
# WP Version
|
# WP Version
|
||||||
class Version < WpItem
|
class Version < WpItem
|
||||||
# @return [ String ]
|
# @return [ Hash ]
|
||||||
def self.db_file
|
def self.metadata
|
||||||
@db_file ||= DB_DIR.join('wordpresses.json').to_s
|
@metadata ||= super['wordpress'] || {}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -12,5 +12,6 @@ end
|
|||||||
require_relative 'errors/enumeration'
|
require_relative 'errors/enumeration'
|
||||||
require_relative 'errors/http'
|
require_relative 'errors/http'
|
||||||
require_relative 'errors/update'
|
require_relative 'errors/update'
|
||||||
|
require_relative 'errors/vuln_api'
|
||||||
require_relative 'errors/wordpress'
|
require_relative 'errors/wordpress'
|
||||||
require_relative 'errors/xmlrpc'
|
require_relative 'errors/xmlrpc'
|
||||||
|
|||||||
20
lib/wpscan/errors/vuln_api.rb
Normal file
20
lib/wpscan/errors/vuln_api.rb
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module WPScan
|
||||||
|
module Error
|
||||||
|
# Error raised when the token given via --api-token is invalid
|
||||||
|
class InvalidApiToken < Standard
|
||||||
|
def to_s
|
||||||
|
'The API token provided is invalid'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Error raised when the number of API requests has been reached
|
||||||
|
# currently not implemented on the API side
|
||||||
|
class ApiLimitReached < Standard
|
||||||
|
def to_s
|
||||||
|
'Your API limit has been reached'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
13
lib/wpscan/typhoeus/response.rb
Normal file
13
lib/wpscan/typhoeus/response.rb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Typhoeus
|
||||||
|
# Custom Response class
|
||||||
|
class Response
|
||||||
|
# @note: Ignores requests done to the /status endpoint of the API
|
||||||
|
#
|
||||||
|
# @return [ Boolean ]
|
||||||
|
def from_vuln_api?
|
||||||
|
effective_url.start_with?(WPScan::DB::VulnApi.uri.to_s) && !effective_url.include?('/status')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
|
|
||||||
# Version
|
# Version
|
||||||
module WPScan
|
module WPScan
|
||||||
VERSION = '3.6.3'
|
VERSION = '3.7.0-dev'
|
||||||
end
|
end
|
||||||
|
|||||||
93
spec/app/controllers/vuln_api_spec.rb
Normal file
93
spec/app/controllers/vuln_api_spec.rb
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
describe WPScan::Controller::VulnApi do
|
||||||
|
subject(:controller) { described_class.new }
|
||||||
|
let(:target_url) { 'http://ex.lo/' }
|
||||||
|
let(:cli_args) { "--url #{target_url}" }
|
||||||
|
|
||||||
|
before do
|
||||||
|
WPScan::ParsedCli.options = rspec_parsed_options(cli_args)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#cli_options' do
|
||||||
|
its(:cli_options) { should_not be_empty }
|
||||||
|
its(:cli_options) { should be_a Array }
|
||||||
|
|
||||||
|
it 'contains to correct options' do
|
||||||
|
expect(controller.cli_options.map(&:to_sym)).to eq %i[api_token]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#before_scan' do
|
||||||
|
context 'when no --api-token provided' do
|
||||||
|
its(:before_scan) { should be nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when --api-token given' do
|
||||||
|
let(:cli_args) { "#{super()} --api-token token" }
|
||||||
|
|
||||||
|
context 'when the token is invalid' do
|
||||||
|
before { expect(WPScan::DB::VulnApi).to receive(:status).and_return('error' => 'HTTP Token: Access denied.') }
|
||||||
|
|
||||||
|
it 'raise an InvalidApiToken error' do
|
||||||
|
expect { controller.before_scan }.to raise_error(WPScan::Error::InvalidApiToken)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the token is valid' do
|
||||||
|
context 'when the limit has been reached' do
|
||||||
|
before do
|
||||||
|
expect(WPScan::DB::VulnApi)
|
||||||
|
.to receive(:status)
|
||||||
|
.and_return('success' => true, 'plan' => 'free', 'requests_remaining' => 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises an ApiLimitReached error' do
|
||||||
|
expect { controller.before_scan }.to raise_error(WPScan::Error::ApiLimitReached)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a HTTP error, like a timeout' do
|
||||||
|
before do
|
||||||
|
expect(WPScan::DB::VulnApi)
|
||||||
|
.to receive(:status)
|
||||||
|
.and_return(
|
||||||
|
'http_error' => WPScan::Error::HTTP.new(
|
||||||
|
Typhoeus::Response.new(effective_url: 'mock-url', return_code: 28)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'raises an HTTP error' do
|
||||||
|
expect { controller.before_scan }
|
||||||
|
.to raise_error(WPScan::Error::HTTP, 'HTTP Error: mock-url (Timeout was reached)')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the token is valid and no HTTP error' do
|
||||||
|
before do
|
||||||
|
expect(WPScan::DB::VulnApi)
|
||||||
|
.to receive(:status)
|
||||||
|
.and_return('success' => true, 'plan' => 'free', 'requests_remaining' => requests)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when limited requests' do
|
||||||
|
let(:requests) { 100 }
|
||||||
|
|
||||||
|
it 'does not raise an error' do
|
||||||
|
expect { controller.before_scan }.to_not raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when unlimited requests' do
|
||||||
|
let(:requests) { 'Unlimited' }
|
||||||
|
|
||||||
|
it 'does not raise an error' do
|
||||||
|
expect { controller.before_scan }.to_not raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -81,24 +81,39 @@ describe WPScan::Model::Plugin do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe '#latest_version, #last_updated, #popular' do
|
describe '#latest_version, #last_updated, #popular' do
|
||||||
context 'when none' do
|
before { allow(plugin).to receive(:db_data).and_return(db_data) }
|
||||||
let(:slug) { 'vulnerable-not-popular' }
|
|
||||||
|
context 'when no db_data and no metadata' do
|
||||||
|
let(:slug) { 'not-known' }
|
||||||
|
let(:db_data) { {} }
|
||||||
|
|
||||||
its(:latest_version) { should be_nil }
|
its(:latest_version) { should be_nil }
|
||||||
its(:last_updated) { should be_nil }
|
its(:last_updated) { should be_nil }
|
||||||
its(:popular?) { should be false }
|
its(:popular?) { should be false }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when values' do
|
context 'when no db_data but metadata' do
|
||||||
let(:slug) { 'no-vulns-popular' }
|
let(:slug) { 'no-vulns-popular' }
|
||||||
|
let(:db_data) { {} }
|
||||||
|
|
||||||
its(:latest_version) { should eql WPScan::Model::Version.new('2.0') }
|
its(:latest_version) { should eql WPScan::Model::Version.new('2.0') }
|
||||||
its(:last_updated) { should eql '2015-05-16T00:00:00.000Z' }
|
its(:last_updated) { should eql '2015-05-16T00:00:00.000Z' }
|
||||||
its(:popular?) { should be true }
|
its(:popular?) { should be true }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when db_data' do
|
||||||
|
let(:slug) { 'no-vulns-popular' }
|
||||||
|
let(:db_data) { vuln_api_data_for('plugins/no-vulns-popular') }
|
||||||
|
|
||||||
|
its(:latest_version) { should eql WPScan::Model::Version.new('2.1') }
|
||||||
|
its(:last_updated) { should eql '2015-05-16T00:00:00.000Z-via-api' }
|
||||||
|
its(:popular?) { should be true }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#outdated?' do
|
describe '#outdated?' do
|
||||||
|
before { allow(plugin).to receive(:db_data).and_return({}) }
|
||||||
|
|
||||||
context 'when last_version' do
|
context 'when last_version' do
|
||||||
let(:slug) { 'no-vulns-popular' }
|
let(:slug) { 'no-vulns-popular' }
|
||||||
|
|
||||||
@@ -116,13 +131,13 @@ describe WPScan::Model::Plugin do
|
|||||||
.and_return(WPScan::Model::Version.new(version_number))
|
.and_return(WPScan::Model::Version.new(version_number))
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when version < last_version' do
|
context 'when version < latest_version' do
|
||||||
let(:version_number) { '1.2' }
|
let(:version_number) { '1.2' }
|
||||||
|
|
||||||
its(:outdated?) { should eql true }
|
its(:outdated?) { should eql true }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when version >= last_version' do
|
context 'when version >= latest_version' do
|
||||||
let(:version_number) { '3.0' }
|
let(:version_number) { '3.0' }
|
||||||
|
|
||||||
its(:outdated?) { should eql false }
|
its(:outdated?) { should eql false }
|
||||||
@@ -130,7 +145,7 @@ describe WPScan::Model::Plugin do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when no last_version' do
|
context 'when no latest_version' do
|
||||||
let(:slug) { 'vulnerable-not-popular' }
|
let(:slug) { 'vulnerable-not-popular' }
|
||||||
|
|
||||||
context 'when no version' do
|
context 'when no version' do
|
||||||
@@ -153,13 +168,16 @@ describe WPScan::Model::Plugin do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe '#vulnerabilities' do
|
describe '#vulnerabilities' do
|
||||||
|
before { allow(plugin).to receive(:db_data).and_return(db_data) }
|
||||||
|
|
||||||
after do
|
after do
|
||||||
expect(plugin.vulnerabilities).to eq @expected
|
expect(plugin.vulnerabilities).to eq @expected
|
||||||
expect(plugin.vulnerable?).to eql @expected.empty? ? false : true
|
expect(plugin.vulnerable?).to eql @expected.empty? ? false : true
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when plugin not in the DB' do
|
context 'when plugin not in the DB' do
|
||||||
let(:slug) { 'not-in-db' }
|
let(:slug) { 'not-in-db' }
|
||||||
|
let(:db_data) { {} }
|
||||||
|
|
||||||
it 'returns an empty array' do
|
it 'returns an empty array' do
|
||||||
@expected = []
|
@expected = []
|
||||||
@@ -168,7 +186,8 @@ describe WPScan::Model::Plugin do
|
|||||||
|
|
||||||
context 'when in the DB' do
|
context 'when in the DB' do
|
||||||
context 'when no vulnerabilities' do
|
context 'when no vulnerabilities' do
|
||||||
let(:slug) { 'no-vulns-popular' }
|
let(:slug) { 'no-vulns-popular' }
|
||||||
|
let(:db_data) { vuln_api_data_for('plugins/no-vulns-popular') }
|
||||||
|
|
||||||
it 'returns an empty array' do
|
it 'returns an empty array' do
|
||||||
@expected = []
|
@expected = []
|
||||||
@@ -176,11 +195,13 @@ describe WPScan::Model::Plugin do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context 'when vulnerabilities' do
|
context 'when vulnerabilities' do
|
||||||
let(:slug) { 'vulnerable-not-popular' }
|
let(:slug) { 'vulnerable-not-popular' }
|
||||||
|
let(:db_data) { vuln_api_data_for('plugins/vulnerable-not-popular') }
|
||||||
|
|
||||||
let(:all_vulns) do
|
let(:all_vulns) do
|
||||||
[
|
[
|
||||||
WPScan::Vulnerability.new(
|
WPScan::Vulnerability.new(
|
||||||
'First Vuln',
|
'First Vuln <= 6.3.10 - LFI',
|
||||||
{ wpvulndb: '1' },
|
{ wpvulndb: '1' },
|
||||||
'LFI',
|
'LFI',
|
||||||
'6.3.10'
|
'6.3.10'
|
||||||
|
|||||||
@@ -86,8 +86,179 @@ describe WPScan::Model::Theme do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#latest_version, #last_updated, #popular' do
|
||||||
|
before do
|
||||||
|
stub_request(:get, /.*\.css\z/)
|
||||||
|
allow(theme).to receive(:db_data).and_return(db_data)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when no db_data and no metadata' do
|
||||||
|
let(:slug) { 'not-known' }
|
||||||
|
let(:db_data) { {} }
|
||||||
|
|
||||||
|
its(:latest_version) { should be_nil }
|
||||||
|
its(:last_updated) { should be_nil }
|
||||||
|
its(:popular?) { should be false }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when no db_data but metadata' do
|
||||||
|
let(:slug) { 'no-vulns-popular' }
|
||||||
|
let(:db_data) { {} }
|
||||||
|
|
||||||
|
its(:latest_version) { should eql WPScan::Model::Version.new('2.0') }
|
||||||
|
its(:last_updated) { should eql '2015-05-16T00:00:00.000Z' }
|
||||||
|
its(:popular?) { should be true }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when db_data' do
|
||||||
|
let(:slug) { 'no-vulns-popular' }
|
||||||
|
let(:db_data) { vuln_api_data_for('themes/no-vulns-popular') }
|
||||||
|
|
||||||
|
its(:latest_version) { should eql WPScan::Model::Version.new('2.2') }
|
||||||
|
its(:last_updated) { should eql '2015-05-16T00:00:00.000Z-via-api' }
|
||||||
|
its(:popular?) { should be true }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#outdated?' do
|
||||||
|
before do
|
||||||
|
stub_request(:get, /.*\.css\z/)
|
||||||
|
allow(theme).to receive(:db_data).and_return({})
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when last_version' do
|
||||||
|
let(:slug) { 'no-vulns-popular' }
|
||||||
|
|
||||||
|
context 'when no version' do
|
||||||
|
before { expect(theme).to receive(:version).at_least(1).and_return(nil) }
|
||||||
|
|
||||||
|
its(:outdated?) { should eql false }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when version' do
|
||||||
|
before do
|
||||||
|
expect(theme)
|
||||||
|
.to receive(:version)
|
||||||
|
.at_least(1)
|
||||||
|
.and_return(WPScan::Model::Version.new(version_number))
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when version < latest_version' do
|
||||||
|
let(:version_number) { '1.2' }
|
||||||
|
|
||||||
|
its(:outdated?) { should eql true }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when version >= latest_version' do
|
||||||
|
let(:version_number) { '3.0' }
|
||||||
|
|
||||||
|
its(:outdated?) { should eql false }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when no latest_version' do
|
||||||
|
let(:slug) { 'vulnerable-not-popular' }
|
||||||
|
|
||||||
|
context 'when no version' do
|
||||||
|
before { expect(theme).to receive(:version).at_least(1).and_return(nil) }
|
||||||
|
|
||||||
|
its(:outdated?) { should eql false }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when version' do
|
||||||
|
before do
|
||||||
|
expect(theme)
|
||||||
|
.to receive(:version)
|
||||||
|
.at_least(1)
|
||||||
|
.and_return(WPScan::Model::Version.new('1.0'))
|
||||||
|
end
|
||||||
|
|
||||||
|
its(:outdated?) { should eql false }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#vulnerabilities' do
|
describe '#vulnerabilities' do
|
||||||
xit
|
before do
|
||||||
|
stub_request(:get, /.*\.css\z/)
|
||||||
|
allow(theme).to receive(:db_data).and_return(db_data)
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
expect(theme.vulnerabilities).to eq @expected
|
||||||
|
expect(theme.vulnerable?).to eql @expected.empty? ? false : true
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when theme not in the DB' do
|
||||||
|
let(:slug) { 'not-in-db' }
|
||||||
|
let(:db_data) { {} }
|
||||||
|
|
||||||
|
it 'returns an empty array' do
|
||||||
|
@expected = []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when in the DB' do
|
||||||
|
context 'when no vulnerabilities' do
|
||||||
|
let(:slug) { 'no-vulns-popular' }
|
||||||
|
let(:db_data) { vuln_api_data_for('themes/no-vulns-popular') }
|
||||||
|
|
||||||
|
it 'returns an empty array' do
|
||||||
|
@expected = []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when vulnerabilities' do
|
||||||
|
let(:slug) { 'vulnerable-not-popular' }
|
||||||
|
let(:db_data) { vuln_api_data_for('themes/vulnerable-not-popular') }
|
||||||
|
|
||||||
|
let(:all_vulns) do
|
||||||
|
[
|
||||||
|
WPScan::Vulnerability.new(
|
||||||
|
'First Vuln',
|
||||||
|
{ wpvulndb: '1' },
|
||||||
|
'LFI',
|
||||||
|
'6.3.10'
|
||||||
|
),
|
||||||
|
WPScan::Vulnerability.new('No Fixed In', wpvulndb: '2')
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when no theme version' do
|
||||||
|
before { expect(theme).to receive(:version).at_least(1).and_return(false) }
|
||||||
|
|
||||||
|
it 'returns all the vulnerabilities' do
|
||||||
|
@expected = all_vulns
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when theme version' do
|
||||||
|
before do
|
||||||
|
expect(theme)
|
||||||
|
.to receive(:version)
|
||||||
|
.at_least(1)
|
||||||
|
.and_return(WPScan::Model::Version.new(number))
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when < to a fixed_in' do
|
||||||
|
let(:number) { '5.0' }
|
||||||
|
|
||||||
|
it 'returns it' do
|
||||||
|
@expected = all_vulns
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when >= to a fixed_in' do
|
||||||
|
let(:number) { '6.3.10' }
|
||||||
|
|
||||||
|
it 'does not return it ' do
|
||||||
|
@expected = [all_vulns.last]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#parent_theme' do
|
describe '#parent_theme' do
|
||||||
|
|||||||
@@ -40,11 +40,13 @@ describe WPScan::Model::WpVersion do
|
|||||||
|
|
||||||
describe '#vulnerabilities' do
|
describe '#vulnerabilities' do
|
||||||
subject(:version) { described_class.new(number) }
|
subject(:version) { described_class.new(number) }
|
||||||
|
before { allow(version).to receive(:db_data).and_return(db_data) }
|
||||||
|
|
||||||
context 'when no vulns' do
|
context 'when no vulns' do
|
||||||
let(:number) { '4.4' }
|
let(:number) { '4.4' }
|
||||||
|
let(:db_data) { { 'vulnerabilities' => [] } }
|
||||||
|
|
||||||
its(:vulnerabilities) { should eql([]) }
|
its(:vulnerabilities) { should be_empty }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when vulnerable' do
|
context 'when vulnerable' do
|
||||||
@@ -53,8 +55,25 @@ describe WPScan::Model::WpVersion do
|
|||||||
expect(version).to be_vulnerable
|
expect(version).to be_vulnerable
|
||||||
end
|
end
|
||||||
|
|
||||||
|
let(:all_vulns) do
|
||||||
|
[
|
||||||
|
WPScan::Vulnerability.new(
|
||||||
|
'WP 3.8.1 - Vuln 1',
|
||||||
|
{ wpvulndb: '1' },
|
||||||
|
'SQLI'
|
||||||
|
),
|
||||||
|
WPScan::Vulnerability.new(
|
||||||
|
'WP 3.8.1 - Vuln 2',
|
||||||
|
{ url: %w[url-2 url-3], osvdb: %w[10], cve: %w[2014-0166], wpvulndb: '2' },
|
||||||
|
nil,
|
||||||
|
'3.8.2'
|
||||||
|
)
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
context 'when a signle vuln' do
|
context 'when a signle vuln' do
|
||||||
let(:number) { '3.8' }
|
let(:number) { '3.8.1' }
|
||||||
|
let(:db_data) { vuln_api_data_for('wordpresses/38') }
|
||||||
|
|
||||||
it 'returns the expected result' do
|
it 'returns the expected result' do
|
||||||
@expected = [WPScan::Vulnerability.new(
|
@expected = [WPScan::Vulnerability.new(
|
||||||
@@ -67,6 +86,7 @@ describe WPScan::Model::WpVersion do
|
|||||||
|
|
||||||
context 'when multiple vulns' do
|
context 'when multiple vulns' do
|
||||||
let(:number) { '3.8.1' }
|
let(:number) { '3.8.1' }
|
||||||
|
let(:db_data) { vuln_api_data_for('wordpresses/381') }
|
||||||
|
|
||||||
it 'returns the expected results' do
|
it 'returns the expected results' do
|
||||||
@expected = [
|
@expected = [
|
||||||
@@ -87,27 +107,30 @@ describe WPScan::Model::WpVersion do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#release_date' do
|
describe '#metadata, #release_date, #status' do
|
||||||
subject(:version) { described_class.new('3.8.1') }
|
subject(:version) { described_class.new('3.8.1') }
|
||||||
|
|
||||||
its(:release_date) { should eql '2014-01-23' }
|
before { allow(version).to receive(:db_data).and_return(db_data) }
|
||||||
|
|
||||||
context 'when the version is not in the DB' do
|
context 'when no db_data' do
|
||||||
subject(:version) { described_class.new('3.8.2') }
|
let(:db_data) { {} }
|
||||||
|
|
||||||
its(:release_date) { should eql 'Unknown' }
|
its(:release_date) { should eql '2014-01-23' }
|
||||||
|
its(:status) { should eql 'outdated' }
|
||||||
|
|
||||||
|
context 'when the version is not in the metadata' do
|
||||||
|
subject(:version) { described_class.new('3.8.2') }
|
||||||
|
|
||||||
|
its(:release_date) { should eql 'Unknown' }
|
||||||
|
its(:status) { should eql 'Unknown' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
describe '#status' do
|
context 'when db_data' do
|
||||||
subject(:version) { described_class.new('3.8.1') }
|
let(:db_data) { vuln_api_data_for('wordpresses/381') }
|
||||||
|
|
||||||
its(:status) { should eql 'outdated' }
|
its(:release_date) { should eql '2014-01-23-via-api' }
|
||||||
|
its(:status) { should eql 'outdated-via-api' }
|
||||||
context 'when the version is not in the DB' do
|
|
||||||
subject(:version) { described_class.new('3.8.2') }
|
|
||||||
|
|
||||||
its(:release_date) { should eql 'Unknown' }
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ describe 'App::Views' do
|
|||||||
# in the expected output.
|
# in the expected output.
|
||||||
%i[JSON CliNoColour].each do |formatter|
|
%i[JSON CliNoColour].each do |formatter|
|
||||||
context "when #{formatter}" do
|
context "when #{formatter}" do
|
||||||
|
it_behaves_like 'App::Views::VulnApi'
|
||||||
it_behaves_like 'App::Views::WpVersion'
|
it_behaves_like 'App::Views::WpVersion'
|
||||||
it_behaves_like 'App::Views::MainTheme'
|
it_behaves_like 'App::Views::MainTheme'
|
||||||
it_behaves_like 'App::Views::Enumeration'
|
it_behaves_like 'App::Views::Enumeration'
|
||||||
|
|||||||
56
spec/fixtures/db/metadata.json
vendored
Normal file
56
spec/fixtures/db/metadata.json
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"wordpress": {
|
||||||
|
"4.0": {
|
||||||
|
"release_date": "2014-09-04",
|
||||||
|
"status": "latest"
|
||||||
|
},
|
||||||
|
"3.8.1": {
|
||||||
|
"release_date": "2014-01-23",
|
||||||
|
"status": "outdated"
|
||||||
|
},
|
||||||
|
"3.8": {
|
||||||
|
"release_date": "2013-12-12",
|
||||||
|
"status": "insecure"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"no-vulns-popular": {
|
||||||
|
"vulnerabilities": false,
|
||||||
|
"popular": true,
|
||||||
|
"latest_version": "2.0",
|
||||||
|
"last_updated": "2015-05-16T00:00:00.000Z"
|
||||||
|
},
|
||||||
|
"vulnerable-not-popular": {
|
||||||
|
"latest_version": null,
|
||||||
|
"last_updated": null,
|
||||||
|
"popular": false,
|
||||||
|
"vulnerabilities": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"themes": {
|
||||||
|
"no-vulns-popular": {
|
||||||
|
"popular": true,
|
||||||
|
"latest_version": "2.0",
|
||||||
|
"last_updated": "2015-05-16T00:00:00.000Z",
|
||||||
|
"vulnerabilities": false
|
||||||
|
},
|
||||||
|
"vulnerable-not-popular": {
|
||||||
|
"latest_version": null,
|
||||||
|
"last_updated": null,
|
||||||
|
"popular": false,
|
||||||
|
"vulnerabilities": true
|
||||||
|
},
|
||||||
|
"dignitas-themes": {
|
||||||
|
"popular": true,
|
||||||
|
"latest_version": null,
|
||||||
|
"last_updated": null,
|
||||||
|
"vulnerabilities" : true
|
||||||
|
},
|
||||||
|
"yaaburnee-themes": {
|
||||||
|
"popular": false,
|
||||||
|
"latest_version": null,
|
||||||
|
"last_updated": null,
|
||||||
|
"vulnerabilities" : true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
spec/fixtures/db/plugins.json
vendored
25
spec/fixtures/db/plugins.json
vendored
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"no-vulns-popular": {
|
|
||||||
"vulnerabilities": [],
|
|
||||||
"popular": true,
|
|
||||||
"latest_version": "2.0",
|
|
||||||
"last_updated": "2015-05-16T00:00:00.000Z"
|
|
||||||
},
|
|
||||||
"vulnerable-not-popular": {
|
|
||||||
"latest_version": null,
|
|
||||||
"last_updated": null,
|
|
||||||
"popular": false,
|
|
||||||
"vulnerabilities" : [
|
|
||||||
{
|
|
||||||
"title" : "First Vuln",
|
|
||||||
"fixed_in" : "6.3.10",
|
|
||||||
"id" : 1,
|
|
||||||
"vuln_type": "LFI"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "No Fixed In",
|
|
||||||
"id": 2
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
52
spec/fixtures/db/themes.json
vendored
52
spec/fixtures/db/themes.json
vendored
@@ -1,52 +0,0 @@
|
|||||||
{
|
|
||||||
"no-vulns-popular": {
|
|
||||||
"popular": true,
|
|
||||||
"latest_version": "2.0",
|
|
||||||
"last_updated": "2015-05-16T00:00:00.000Z",
|
|
||||||
"vulnerabilities": []
|
|
||||||
},
|
|
||||||
"dignitas-themes": {
|
|
||||||
"popular": true,
|
|
||||||
"latest_version": null,
|
|
||||||
"last_updated": null,
|
|
||||||
"vulnerabilities" : [
|
|
||||||
{
|
|
||||||
"created_at" : "2015-03-05T19:25:59.000Z",
|
|
||||||
"updated_at" : "2015-03-05T19:37:47.000Z",
|
|
||||||
"references": {
|
|
||||||
"url" : [
|
|
||||||
"http://research.evex.pw/?vuln=6"
|
|
||||||
],
|
|
||||||
"packetstorm": [
|
|
||||||
"130652"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"title" : "Dignitas 1.1.9 - Privilage Escalation",
|
|
||||||
"id" : 7825,
|
|
||||||
"vuln_type" : "AUTHBYPASS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"yaaburnee-themes": {
|
|
||||||
"popular": false,
|
|
||||||
"latest_version": null,
|
|
||||||
"last_updated": null,
|
|
||||||
"vulnerabilities" : [
|
|
||||||
{
|
|
||||||
"created_at" : "2015-03-05T19:25:44.000Z",
|
|
||||||
"updated_at" : "2015-03-05T19:41:14.000Z",
|
|
||||||
"references": {
|
|
||||||
"url" : [
|
|
||||||
"http://research.evex.pw/?vuln=6"
|
|
||||||
],
|
|
||||||
"packetstorm": [
|
|
||||||
"130652"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"title" : "Ya'aburnee 1.0.7 - Privilage Escalation",
|
|
||||||
"id" : 7824,
|
|
||||||
"vuln_type" : "AUTHBYPASS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
6
spec/fixtures/db/vuln_api/plugins/no-vulns-popular.json
vendored
Normal file
6
spec/fixtures/db/vuln_api/plugins/no-vulns-popular.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"vulnerabilities": [],
|
||||||
|
"popular": true,
|
||||||
|
"latest_version": "2.1",
|
||||||
|
"last_updated": "2015-05-16T00:00:00.000Z-via-api"
|
||||||
|
}
|
||||||
17
spec/fixtures/db/vuln_api/plugins/vulnerable-not-popular.json
vendored
Normal file
17
spec/fixtures/db/vuln_api/plugins/vulnerable-not-popular.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"latest_version": null,
|
||||||
|
"last_updated": null,
|
||||||
|
"popular": false,
|
||||||
|
"vulnerabilities" : [
|
||||||
|
{
|
||||||
|
"title" : "First Vuln \u003c= 6.3.10 - LFI",
|
||||||
|
"fixed_in" : "6.3.10",
|
||||||
|
"id" : 1,
|
||||||
|
"vuln_type": "LFI"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "No Fixed In",
|
||||||
|
"id": 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
20
spec/fixtures/db/vuln_api/themes/dignitas-themes.json
vendored
Normal file
20
spec/fixtures/db/vuln_api/themes/dignitas-themes.json
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"popular": true,
|
||||||
|
"latest_version": null,
|
||||||
|
"last_updated": null,
|
||||||
|
"vulnerabilities" : [
|
||||||
|
{
|
||||||
|
"created_at" : "2015-03-05T19:25:59.000Z",
|
||||||
|
"updated_at" : "2015-03-05T19:37:47.000Z",
|
||||||
|
"references": {
|
||||||
|
"url" : [
|
||||||
|
"http://research.evex.pw/?vuln=6",
|
||||||
|
"http://packetstormsecurity.com/files/130652/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"title" : "Dignitas 1.1.9 - Privilage Escalation",
|
||||||
|
"id" : 7825,
|
||||||
|
"vuln_type" : "AUTHBYPASS"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
6
spec/fixtures/db/vuln_api/themes/no-vulns-popular.json
vendored
Normal file
6
spec/fixtures/db/vuln_api/themes/no-vulns-popular.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"popular": true,
|
||||||
|
"latest_version": "2.2",
|
||||||
|
"last_updated": "2015-05-16T00:00:00.000Z-via-api",
|
||||||
|
"vulnerabilities": []
|
||||||
|
}
|
||||||
17
spec/fixtures/db/vuln_api/themes/vulnerable-not-popular.json
vendored
Normal file
17
spec/fixtures/db/vuln_api/themes/vulnerable-not-popular.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"latest_version": null,
|
||||||
|
"last_updated": null,
|
||||||
|
"popular": false,
|
||||||
|
"vulnerabilities" : [
|
||||||
|
{
|
||||||
|
"title" : "First Vuln",
|
||||||
|
"fixed_in" : "6.3.10",
|
||||||
|
"id" : 1,
|
||||||
|
"vuln_type": "LFI"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "No Fixed In",
|
||||||
|
"id": 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
20
spec/fixtures/db/vuln_api/themes/yaaburnee-themes.json
vendored
Normal file
20
spec/fixtures/db/vuln_api/themes/yaaburnee-themes.json
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"popular": false,
|
||||||
|
"latest_version": null,
|
||||||
|
"last_updated": null,
|
||||||
|
"vulnerabilities" : [
|
||||||
|
{
|
||||||
|
"created_at" : "2015-03-05T19:25:44.000Z",
|
||||||
|
"updated_at" : "2015-03-05T19:41:14.000Z",
|
||||||
|
"references": {
|
||||||
|
"url" : [
|
||||||
|
"http://research.evex.pw/?vuln=6",
|
||||||
|
"http://packetstormsecurity.com/files/130652/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"title" : "Ya'aburnee 1.0.7 - Privilage Escalation",
|
||||||
|
"id" : 7824,
|
||||||
|
"vuln_type" : "AUTHBYPASS"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
17
spec/fixtures/db/vuln_api/wordpresses/38.json
vendored
Normal file
17
spec/fixtures/db/vuln_api/wordpresses/38.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"release_date" : "2013-12-12",
|
||||||
|
"status": "insecure",
|
||||||
|
"vulnerabilities" : [
|
||||||
|
{
|
||||||
|
"references": {
|
||||||
|
"url" : ["url-4"],
|
||||||
|
"osvdb" : ["11"]
|
||||||
|
},
|
||||||
|
"created_at" : "2014-08-01T10:58:19.000Z",
|
||||||
|
"updated_at" : "2014-09-16T15:45:26.000Z",
|
||||||
|
"title" : "WP 3.8 - Vuln 1",
|
||||||
|
"id" : 3,
|
||||||
|
"vuln_type" : "AUTHBYPASS"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
27
spec/fixtures/db/vuln_api/wordpresses/381.json
vendored
Normal file
27
spec/fixtures/db/vuln_api/wordpresses/381.json
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"release_date" : "2014-01-23-via-api",
|
||||||
|
"status": "outdated-via-api",
|
||||||
|
"vulnerabilities" : [
|
||||||
|
{
|
||||||
|
"created_at" : "2014-08-01T10:58:19.000Z",
|
||||||
|
"updated_at" : "2014-09-16T13:52:17.000Z",
|
||||||
|
"title" : "WP 3.8.1 - Vuln 1",
|
||||||
|
"id" : 1,
|
||||||
|
"vuln_type" : "SQLI",
|
||||||
|
"published_date" : null,
|
||||||
|
"fixed_in" : null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"references" : {
|
||||||
|
"cve" : ["2014-0166"],
|
||||||
|
"osvdb" : ["10"],
|
||||||
|
"url" : ["url-2","url-3"]
|
||||||
|
},
|
||||||
|
"fixed_in" : "3.8.2",
|
||||||
|
"created_at" : "2014-08-01T10:58:19.000Z",
|
||||||
|
"updated_at" : "2014-09-16T13:53:11.000Z",
|
||||||
|
"id" : 2,
|
||||||
|
"title" : "WP 3.8.1 - Vuln 2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
4
spec/fixtures/db/vuln_api/wordpresses/40.json
vendored
Normal file
4
spec/fixtures/db/vuln_api/wordpresses/40.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"release_date" : "2014-09-04",
|
||||||
|
"status": "latest"
|
||||||
|
}
|
||||||
48
spec/fixtures/db/wordpresses.json
vendored
48
spec/fixtures/db/wordpresses.json
vendored
@@ -1,48 +0,0 @@
|
|||||||
{
|
|
||||||
"4.0": {
|
|
||||||
"release_date" : "2014-09-04",
|
|
||||||
"status": "latest"
|
|
||||||
},
|
|
||||||
"3.8.1": {
|
|
||||||
"release_date" : "2014-01-23",
|
|
||||||
"status": "outdated",
|
|
||||||
"vulnerabilities" : [
|
|
||||||
{
|
|
||||||
"created_at" : "2014-08-01T10:58:19.000Z",
|
|
||||||
"updated_at" : "2014-09-16T13:52:17.000Z",
|
|
||||||
"title" : "WP 3.8.1 - Vuln 1",
|
|
||||||
"id" : 1,
|
|
||||||
"vuln_type" : "SQLI",
|
|
||||||
"published_date" : null,
|
|
||||||
"fixed_in" : null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"references" : {
|
|
||||||
"cve" : ["2014-0166"],
|
|
||||||
"url" : ["url-2","url-3"]
|
|
||||||
},
|
|
||||||
"fixed_in" : "3.8.2",
|
|
||||||
"created_at" : "2014-08-01T10:58:19.000Z",
|
|
||||||
"updated_at" : "2014-09-16T13:53:11.000Z",
|
|
||||||
"id" : 2,
|
|
||||||
"title" : "WP 3.8.1 - Vuln 2"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"3.8": {
|
|
||||||
"release_date" : "2013-12-12",
|
|
||||||
"status": "insecure",
|
|
||||||
"vulnerabilities" : [
|
|
||||||
{
|
|
||||||
"references": {
|
|
||||||
"url" : ["url-4"]
|
|
||||||
},
|
|
||||||
"created_at" : "2014-08-01T10:58:19.000Z",
|
|
||||||
"updated_at" : "2014-09-16T15:45:26.000Z",
|
|
||||||
"title" : "WP 3.8 - Vuln 1",
|
|
||||||
"id" : 3,
|
|
||||||
"vuln_type" : "AUTHBYPASS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@ describe WPScan::DB::Themes do
|
|||||||
subject(:themes) { described_class }
|
subject(:themes) { described_class }
|
||||||
|
|
||||||
describe '#all_slugs' do
|
describe '#all_slugs' do
|
||||||
its(:all_slugs) { should eql %w[no-vulns-popular dignitas-themes yaaburnee-themes] }
|
its(:all_slugs) { should eql %w[no-vulns-popular vulnerable-not-popular dignitas-themes yaaburnee-themes] }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#popular_slugs' do
|
describe '#popular_slugs' do
|
||||||
@@ -12,6 +12,6 @@ describe WPScan::DB::Themes do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe '#vulnerable_slugs' do
|
describe '#vulnerable_slugs' do
|
||||||
its(:vulnerable_slugs) { should eql %w[dignitas-themes yaaburnee-themes] }
|
its(:vulnerable_slugs) { should eql %w[vulnerable-not-popular dignitas-themes yaaburnee-themes] }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
281
spec/lib/db/vuln_api_spec.rb
Normal file
281
spec/lib/db/vuln_api_spec.rb
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
describe WPScan::DB::VulnApi do
|
||||||
|
subject(:api) { described_class }
|
||||||
|
|
||||||
|
describe '#uri' do
|
||||||
|
its(:uri) { should be_a Addressable::URI }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#token, @token=' do
|
||||||
|
context 'when no token set' do
|
||||||
|
before { api.token = nil } # In case it was set by a previous spec
|
||||||
|
|
||||||
|
its(:token) { should be nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when token set' do
|
||||||
|
it 'returns it' do
|
||||||
|
api.token = 's3cRet'
|
||||||
|
|
||||||
|
expect(api.token).to eql 's3cRet'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#get' do
|
||||||
|
context 'when no token' do
|
||||||
|
before { api.token = nil }
|
||||||
|
|
||||||
|
it 'returns an empty hash' do
|
||||||
|
expect(api.get('test')).to eql({})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a token' do
|
||||||
|
before { api.token = 's3cRet' }
|
||||||
|
|
||||||
|
context 'when no timeouts' do
|
||||||
|
before do
|
||||||
|
stub_request(:get, api.uri.join('path'))
|
||||||
|
.with(headers: { 'Host' => api.uri.host, 'Expect' => nil, 'Referer' => nil,
|
||||||
|
'User-Agent' => WPScan::Browser.instance.default_user_agent,
|
||||||
|
'Authorization' => 'Token token=s3cRet' })
|
||||||
|
.to_return(status: code, body: body)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when 200' do
|
||||||
|
let(:code) { 200 }
|
||||||
|
let(:body) { { data: 'something' }.to_json }
|
||||||
|
|
||||||
|
it 'returns the expected hash' do
|
||||||
|
result = api.get('path')
|
||||||
|
|
||||||
|
expect(result).to eql('data' => 'something')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when 401' do
|
||||||
|
let(:code) { 401 }
|
||||||
|
let(:body) { { error: 'HTTP Token: Access denied.' }.to_json }
|
||||||
|
|
||||||
|
it 'returns the expected hash' do
|
||||||
|
result = api.get('path')
|
||||||
|
|
||||||
|
expect(result).to eql('error' => 'HTTP Token: Access denied.')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when 404' do
|
||||||
|
let(:code) { 404 }
|
||||||
|
let(:body) { { error: 'Not found' }.to_json }
|
||||||
|
|
||||||
|
it 'returns an empty hash' do
|
||||||
|
result = api.get('path')
|
||||||
|
|
||||||
|
expect(result).to eql('error' => 'Not found')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when timeouts' do
|
||||||
|
context 'when all requests timeout' do
|
||||||
|
before do
|
||||||
|
stub_request(:get, api.uri.join('path'))
|
||||||
|
.with(headers: { 'Host' => api.uri.host, 'Expect' => nil, 'Referer' => nil,
|
||||||
|
'User-Agent' => WPScan::Browser.instance.default_user_agent,
|
||||||
|
'Authorization' => 'Token token=s3cRet' })
|
||||||
|
.to_return(status: 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'tries 3 times and returns the hash with the error' do
|
||||||
|
expect(api).to receive(:sleep).with(1).exactly(3).times
|
||||||
|
|
||||||
|
result = api.get('path')
|
||||||
|
|
||||||
|
expect(result['http_error']).to be_a WPScan::Error::HTTP
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when only the first request timeout' do
|
||||||
|
before do
|
||||||
|
stub_request(:get, api.uri.join('path'))
|
||||||
|
.with(headers: { 'Host' => api.uri.host, 'Expect' => nil, 'Referer' => nil,
|
||||||
|
'User-Agent' => WPScan::Browser.instance.default_user_agent,
|
||||||
|
'Authorization' => 'Token token=s3cRet' })
|
||||||
|
.to_return(status: 0).then
|
||||||
|
.to_return(status: 200, body: { data: 'test' }.to_json)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'tries 1 time and returns expected data' do
|
||||||
|
expect(api).to receive(:sleep).with(1).exactly(1).times
|
||||||
|
|
||||||
|
result = api.get('path')
|
||||||
|
|
||||||
|
expect(result).to eql('data' => 'test')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#plugin_data' do
|
||||||
|
before { api.token = api_token }
|
||||||
|
|
||||||
|
context 'when no --api-token' do
|
||||||
|
let(:api_token) { nil }
|
||||||
|
|
||||||
|
it 'returns an empty hash' do
|
||||||
|
expect(api.plugin_data('slug')).to eql({})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when valid --api-token' do
|
||||||
|
let(:api_token) { 's3cRet' }
|
||||||
|
|
||||||
|
context 'when the slug exist' do
|
||||||
|
it 'calls the correct URL' do
|
||||||
|
stub_request(:get, api.uri.join('plugins/slug'))
|
||||||
|
.to_return(status: 200, body: { slug: { p: 'aa' } }.to_json)
|
||||||
|
|
||||||
|
expect(api.plugin_data('slug')).to eql('p' => 'aa')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the slug does not exist' do
|
||||||
|
it 'returns an empty hash' do
|
||||||
|
stub_request(:get, api.uri.join('plugins/slug-404')).to_return(status: 404, body: '{}')
|
||||||
|
|
||||||
|
expect(api.plugin_data('slug-404')).to eql({})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#theme_data' do
|
||||||
|
before { api.token = api_token }
|
||||||
|
|
||||||
|
context 'when no --api-token' do
|
||||||
|
let(:api_token) { nil }
|
||||||
|
|
||||||
|
it 'returns an empty hash' do
|
||||||
|
expect(api.theme_data('slug')).to eql({})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when valid --api-token' do
|
||||||
|
let(:api_token) { 's3cRet' }
|
||||||
|
|
||||||
|
context 'when the slug exist' do
|
||||||
|
it 'calls the correct URL' do
|
||||||
|
stub_request(:get, api.uri.join('themes/slug'))
|
||||||
|
.to_return(status: 200, body: { slug: { t: 'aa' } }.to_json)
|
||||||
|
|
||||||
|
expect(api.theme_data('slug')).to eql('t' => 'aa')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the slug does not exist' do
|
||||||
|
it 'returns an empty hash' do
|
||||||
|
stub_request(:get, api.uri.join('themes/slug-404')).to_return(status: 404, body: '{}')
|
||||||
|
|
||||||
|
expect(api.theme_data('slug-404')).to eql({})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#wordpress_data' do
|
||||||
|
before { api.token = api_token }
|
||||||
|
|
||||||
|
context 'when no --api-token' do
|
||||||
|
let(:api_token) { nil }
|
||||||
|
|
||||||
|
it 'returns an empty hash' do
|
||||||
|
expect(api.wordpress_data('1.2')).to eql({})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when valid --api-token' do
|
||||||
|
let(:api_token) { 's3cRet' }
|
||||||
|
|
||||||
|
context 'when the version exist' do
|
||||||
|
it 'calls the correct URL' do
|
||||||
|
stub_request(:get, api.uri.join('wordpresses/522'))
|
||||||
|
.to_return(status: 200, body: { '5.2.2' => { w: 'aa' } }.to_json)
|
||||||
|
|
||||||
|
expect(api.wordpress_data('5.2.2')).to eql('w' => 'aa')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the version does not exist' do
|
||||||
|
it 'returns an empty hash' do
|
||||||
|
stub_request(:get, api.uri.join('wordpresses/11')).to_return(status: 404, body: '{}')
|
||||||
|
|
||||||
|
expect(api.wordpress_data('1.1')).to eql({})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#status' do
|
||||||
|
before do
|
||||||
|
api.token = 's3cRet'
|
||||||
|
|
||||||
|
stub_request(:get, api.uri.join('status'))
|
||||||
|
.with(query: { version: WPScan::VERSION },
|
||||||
|
headers: { 'Host' => api.uri.host, 'Expect' => nil, 'Referer' => nil,
|
||||||
|
'User-Agent' => WPScan::Browser.instance.default_user_agent,
|
||||||
|
'Authorization' => 'Token token=s3cRet' })
|
||||||
|
.to_return(status: code, body: return_body.to_json)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:code) { 200 }
|
||||||
|
let(:return_body) { {} }
|
||||||
|
|
||||||
|
context 'when 200' do
|
||||||
|
let(:return_body) { { success: true, plan: 'free', requests_remaining: 100 } }
|
||||||
|
|
||||||
|
it 'returns the expected hash' do
|
||||||
|
status = api.status
|
||||||
|
|
||||||
|
expect(status['success']).to be true
|
||||||
|
expect(status['plan']).to eql 'free'
|
||||||
|
expect(status['requests_remaining']).to eql 100
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when unlimited requests' do
|
||||||
|
let(:return_body) { super().merge(plan: 'enterprise', requests_remaining: -1) }
|
||||||
|
|
||||||
|
it 'returns the expected hash, witht he correct requests_remaining' do
|
||||||
|
status = api.status
|
||||||
|
|
||||||
|
expect(status['success']).to be true
|
||||||
|
expect(status['plan']).to eql 'enterprise'
|
||||||
|
expect(status['requests_remaining']).to eql 'Unlimited'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when 401' do
|
||||||
|
let(:code) { 401 }
|
||||||
|
let(:return_body) { { error: 'HTTP Token: Access denied.' } }
|
||||||
|
|
||||||
|
it 'returns the expected hash' do
|
||||||
|
status = api.status
|
||||||
|
|
||||||
|
expect(status['error']).to eql 'HTTP Token: Access denied.'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'otherwise' do
|
||||||
|
let(:code) { 0 }
|
||||||
|
|
||||||
|
it 'returns the expected hash with the response' do
|
||||||
|
status = api.status
|
||||||
|
|
||||||
|
expect(status['http_error']).to be_a WPScan::Error::HTTP
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -83,14 +83,22 @@ describe WPScan::Target do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context 'when wp_version found' do
|
context 'when wp_version found' do
|
||||||
|
before do
|
||||||
|
expect(wp_version)
|
||||||
|
.to receive(:db_data)
|
||||||
|
.and_return(vuln_api_data_for("wordpresses/#{wp_version.number.tr('.', '')}"))
|
||||||
|
|
||||||
|
target.instance_variable_set(:@wp_version, wp_version)
|
||||||
|
end
|
||||||
|
|
||||||
context 'when not vulnerable' do
|
context 'when not vulnerable' do
|
||||||
before { target.instance_variable_set(:@wp_version, WPScan::Model::WpVersion.new('4.4')) }
|
let(:wp_version) { WPScan::Model::WpVersion.new('4.0') }
|
||||||
|
|
||||||
it { should_not be_vulnerable }
|
it { should_not be_vulnerable }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when vulnerable' do
|
context 'when vulnerable' do
|
||||||
before { target.instance_variable_set(:@wp_version, WPScan::Model::WpVersion.new('3.8.1')) }
|
let(:wp_version) { WPScan::Model::WpVersion.new('3.8.1') }
|
||||||
|
|
||||||
it { should be_vulnerable }
|
it { should be_vulnerable }
|
||||||
end
|
end
|
||||||
|
|||||||
4
spec/output/vuln_api/all_ok.cli_no_colour
Normal file
4
spec/output/vuln_api/all_ok.cli_no_colour
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[+] WPVulnDB API OK
|
||||||
|
| Plan: paid
|
||||||
|
| Requests Done (during the scan): 3
|
||||||
|
| Requests Remaining: 120
|
||||||
7
spec/output/vuln_api/all_ok.json
Normal file
7
spec/output/vuln_api/all_ok.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"vuln_api": {
|
||||||
|
"plan": "paid",
|
||||||
|
"requests_done_during_scan": 3,
|
||||||
|
"requests_remaining": 120
|
||||||
|
}
|
||||||
|
}
|
||||||
1
spec/output/vuln_api/http_error.cli_no_colour
Normal file
1
spec/output/vuln_api/http_error.cli_no_colour
Normal file
@@ -0,0 +1 @@
|
|||||||
|
[!] WPVulnDB API, HTTP Error: url (Timeout was reached)
|
||||||
5
spec/output/vuln_api/http_error.json
Normal file
5
spec/output/vuln_api/http_error.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"vuln_api": {
|
||||||
|
"http_error": "HTTP Error: url (Timeout was reached)"
|
||||||
|
}
|
||||||
|
}
|
||||||
4
spec/output/vuln_api/no_more_requests.cli_no_colour
Normal file
4
spec/output/vuln_api/no_more_requests.cli_no_colour
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[+] WPVulnDB API OK
|
||||||
|
| Plan: free
|
||||||
|
| Requests Done (during the scan): 3
|
||||||
|
| Requests Remaining: 0
|
||||||
7
spec/output/vuln_api/no_more_requests.json
Normal file
7
spec/output/vuln_api/no_more_requests.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"vuln_api": {
|
||||||
|
"plan": "free",
|
||||||
|
"requests_done_during_scan": 3,
|
||||||
|
"requests_remaining": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
2
spec/output/vuln_api/no_token.cli_no_colour
Normal file
2
spec/output/vuln_api/no_token.cli_no_colour
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[!] No WPVulnDB API Token given, as a result vulnerability data has not been output.
|
||||||
|
[!] You can get a free API token with 50 daily requests by registering at https://wpvulndb.com/register.
|
||||||
5
spec/output/vuln_api/no_token.json
Normal file
5
spec/output/vuln_api/no_token.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"vuln_api": {
|
||||||
|
"error": "No WPVulnDB API Token given, as a result vulnerability data has not been output.\nYou can get a free API token with 50 daily requests by registering at https://wpvulndb.com/register."
|
||||||
|
}
|
||||||
|
}
|
||||||
4
spec/output/vuln_api/unlimited_requests.cli_no_colour
Normal file
4
spec/output/vuln_api/unlimited_requests.cli_no_colour
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[+] WPVulnDB API OK
|
||||||
|
| Plan: enterprise
|
||||||
|
| Requests Done (during the scan): 3
|
||||||
|
| Requests Remaining: Unlimited
|
||||||
7
spec/output/vuln_api/unlimited_requests.json
Normal file
7
spec/output/vuln_api/unlimited_requests.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"vuln_api": {
|
||||||
|
"plan": "enterprise",
|
||||||
|
"requests_done_during_scan": 3,
|
||||||
|
"requests_remaining": "Unlimited"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
[+] WordPress version 3.8.1 identified (Outdated, released on 2014-01-23).
|
[+] WordPress version 3.8.1 identified (Outdated-via-api, released on 2014-01-23-via-api).
|
||||||
| Detected By: rspec
|
| Detected By: rspec
|
||||||
|
|
|
|
||||||
| [!] 2 vulnerabilities identified:
|
| [!] 2 vulnerabilities identified:
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"version": {
|
"version": {
|
||||||
"number": "3.8.1",
|
"number": "3.8.1",
|
||||||
"release_date": "2014-01-23",
|
"release_date": "2014-01-23-via-api",
|
||||||
"status": "outdated",
|
"status": "outdated-via-api",
|
||||||
"found_by": "rspec",
|
"found_by": "rspec",
|
||||||
"confidence": 0,
|
"confidence": 0,
|
||||||
"interesting_entries": [
|
"interesting_entries": [
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'shared_examples/views/vuln_api'
|
||||||
require 'shared_examples/views/wp_version'
|
require 'shared_examples/views/wp_version'
|
||||||
require 'shared_examples/views/main_theme'
|
require 'shared_examples/views/main_theme'
|
||||||
require 'shared_examples/views/enumeration'
|
require 'shared_examples/views/enumeration'
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ shared_examples 'App::Views::MainTheme' do
|
|||||||
|
|
||||||
it 'outputs the expected string' do
|
it 'outputs the expected string' do
|
||||||
expect(theme).to receive(:version).at_least(1)
|
expect(theme).to receive(:version).at_least(1)
|
||||||
|
allow(theme).to receive(:db_data).and_return(vuln_api_data_for('themes/dignitas-themes'))
|
||||||
|
|
||||||
@tpl_vars = tpl_vars.merge(theme: theme, verbose: true)
|
@tpl_vars = tpl_vars.merge(theme: theme, verbose: true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
63
spec/shared_examples/views/vuln_api.rb
Normal file
63
spec/shared_examples/views/vuln_api.rb
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
shared_examples 'App::Views::VulnApi' do
|
||||||
|
let(:controller) { WPScan::Controller::VulnApi.new }
|
||||||
|
let(:tpl_vars) { { url: target_url } }
|
||||||
|
|
||||||
|
describe 'status' do
|
||||||
|
let(:view) { 'status' }
|
||||||
|
|
||||||
|
context 'when no api token is given' do
|
||||||
|
let(:expected_view) { 'no_token' }
|
||||||
|
|
||||||
|
it 'outputs the expected string' do
|
||||||
|
@tpl_vars = tpl_vars.merge(status: {})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when http error' do
|
||||||
|
let(:expected_view) { 'http_error' }
|
||||||
|
|
||||||
|
it 'outputs the expected string' do
|
||||||
|
@tpl_vars = tpl_vars.merge(
|
||||||
|
status: {
|
||||||
|
'http_error' => WPScan::Error::HTTP.new(Typhoeus::Response.new(effective_url: 'url', return_code: 28))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when no more remaining requests' do
|
||||||
|
let(:expected_view) { 'no_more_requests' }
|
||||||
|
|
||||||
|
it 'outputs the expected string' do
|
||||||
|
@tpl_vars = tpl_vars.merge(
|
||||||
|
status: { 'success': true, 'plan' => 'free', 'requests_remaining' => 0 },
|
||||||
|
api_requests: 3
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when everything is fine' do
|
||||||
|
let(:expected_view) { 'all_ok' }
|
||||||
|
|
||||||
|
it 'outputs the expected string' do
|
||||||
|
@tpl_vars = tpl_vars.merge(
|
||||||
|
status: { 'success': true, 'plan' => 'paid', 'requests_remaining' => 120 },
|
||||||
|
api_requests: 3
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when unlimited requests' do
|
||||||
|
let(:expected_view) { 'unlimited_requests' }
|
||||||
|
|
||||||
|
it 'outputs the expected string' do
|
||||||
|
@tpl_vars = tpl_vars.merge(
|
||||||
|
status: { 'success': true, 'plan' => 'enterprise', 'requests_remaining' => 'Unlimited' },
|
||||||
|
api_requests: 3
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -17,6 +17,7 @@ shared_examples 'App::Views::WpVersion' do
|
|||||||
|
|
||||||
context 'when the version is not nil' do
|
context 'when the version is not nil' do
|
||||||
let(:version) { WPScan::Model::WpVersion.new('4.0', found_by: 'rspec') }
|
let(:version) { WPScan::Model::WpVersion.new('4.0', found_by: 'rspec') }
|
||||||
|
before { allow(version).to receive(:db_data).and_return(vuln_api_data_for('wordpresses/40')) }
|
||||||
|
|
||||||
context 'when confirmed_by is empty' do
|
context 'when confirmed_by is empty' do
|
||||||
context 'when no interesting_entries' do
|
context 'when no interesting_entries' do
|
||||||
@@ -77,9 +78,12 @@ shared_examples 'App::Views::WpVersion' do
|
|||||||
|
|
||||||
context 'when the version is vulnerable' do
|
context 'when the version is vulnerable' do
|
||||||
let(:expected_view) { 'with_vulns' }
|
let(:expected_view) { 'with_vulns' }
|
||||||
|
let(:version) { WPScan::Model::WpVersion.new('3.8.1', found_by: 'rspec') }
|
||||||
|
|
||||||
|
before { allow(version).to receive(:db_data).and_return(vuln_api_data_for('wordpresses/381')) }
|
||||||
|
|
||||||
it 'outputs the expected string' do
|
it 'outputs the expected string' do
|
||||||
@tpl_vars = tpl_vars.merge(version: WPScan::Model::WpVersion.new('3.8.1', found_by: 'rspec'))
|
@tpl_vars = tpl_vars.merge(version: version)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ def df_stubbed_response(fixture, finder_super_class)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def vuln_api_data_for(path)
|
||||||
|
JSON.parse(File.read(FIXTURES.join('db', 'vuln_api', "#{path}.json")))
|
||||||
|
end
|
||||||
|
|
||||||
require 'wpscan'
|
require 'wpscan'
|
||||||
require 'shared_examples'
|
require 'shared_examples'
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user