VulnAPI Implementation
This commit is contained in:
@@ -8,12 +8,12 @@ ClassVars:
|
||||
Enabled: false
|
||||
LineLength:
|
||||
Max: 120
|
||||
Lint/UriEscapeUnescape:
|
||||
Enabled: false
|
||||
MethodLength:
|
||||
Max: 20
|
||||
Exclude:
|
||||
- 'app/controllers/enumeration/cli_options.rb'
|
||||
Lint/UriEscapeUnescape:
|
||||
Enabled: false
|
||||
Metrics/AbcSize:
|
||||
Max: 25
|
||||
Metrics/BlockLength:
|
||||
@@ -29,3 +29,6 @@ Style/Documentation:
|
||||
Enabled: false
|
||||
Style/FormatStringToken:
|
||||
Enabled: false
|
||||
Style/NumericPredicate:
|
||||
Exclude:
|
||||
- 'app/controllers/vuln_api.rb'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'controllers/core'
|
||||
require_relative 'controllers/api_token'
|
||||
require_relative 'controllers/vuln_api'
|
||||
require_relative 'controllers/custom_directories'
|
||||
require_relative 'controllers/wp_version'
|
||||
require_relative 'controllers/main_theme'
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Controller
|
||||
# Controller to handle the API token
|
||||
class ApiToken < CMSScanner::Controller::Base
|
||||
def cli_options
|
||||
[
|
||||
OptString.new(['--api-token TOKEN', 'The API Token to display vulnerability data'])
|
||||
]
|
||||
end
|
||||
|
||||
def before_scan(opts = {})
|
||||
# TODO, validate the token
|
||||
# res = browser.get()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
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))
|
||||
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::Plugin.metadata_at(slug)
|
||||
@metadata ||= db_data.empty? ? DB::Plugin.metadata_at(slug) : db_data
|
||||
end
|
||||
|
||||
# @return [ Hash ]
|
||||
def db_data
|
||||
@db_data ||= DB::VulnApi.plugin_data(slug)
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
|
||||
@@ -21,9 +21,16 @@ module WPScan
|
||||
parse_style
|
||||
end
|
||||
|
||||
# Retrieve the metadata from the vuln API if available (and a valid token is given),
|
||||
# or the local metadata db otherwise
|
||||
# @return [ JSON ]
|
||||
def metadata
|
||||
@metadata ||= DB::Theme.metadata_at(slug)
|
||||
@metadata ||= db_data.empty? ? DB::Theme.metadata_at(slug) : db_data
|
||||
end
|
||||
|
||||
# @return [ Hash ]
|
||||
def db_data
|
||||
@db_data ||= DB::VulnApi.theme_data(slug)
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
|
||||
@@ -39,11 +39,10 @@ module WPScan
|
||||
|
||||
@vulnerabilities = []
|
||||
|
||||
# TODO Get them from API
|
||||
#[*db_data['vulnerabilities']].each do |json_vuln|
|
||||
# vulnerability = Vulnerability.load_from_json(json_vuln)
|
||||
# @vulnerabilities << vulnerability if vulnerable_to?(vulnerability)
|
||||
#end
|
||||
[*db_data['vulnerabilities']].each do |json_vuln|
|
||||
vulnerability = Vulnerability.load_from_json(json_vuln)
|
||||
@vulnerabilities << vulnerability if vulnerable_to?(vulnerability)
|
||||
end
|
||||
|
||||
@vulnerabilities
|
||||
end
|
||||
@@ -67,7 +66,7 @@ module WPScan
|
||||
# Not used anywhere ATM
|
||||
# @return [ Boolean ]
|
||||
def popular?
|
||||
@popular ||= metadata['popular']
|
||||
@popular ||= metadata['popular'] ? true : false
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
|
||||
@@ -35,9 +35,16 @@ module WPScan
|
||||
@all_numbers.sort! { |a, b| Gem::Version.new(b) <=> Gem::Version.new(a) }
|
||||
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::Version.metadata_at(number)
|
||||
@metadata ||= db_data.empty? ? DB::Version.metadata_at(number) : db_data
|
||||
end
|
||||
|
||||
# @return [ Hash ]
|
||||
def db_data
|
||||
@db_data ||= DB::VulnApi.wordpress_data(number)
|
||||
end
|
||||
|
||||
# @return [ Array<Vulnerability> ]
|
||||
@@ -46,10 +53,9 @@ module WPScan
|
||||
|
||||
@vulnerabilities = []
|
||||
|
||||
# TODO get them from API
|
||||
#[*db_data['vulnerabilities']].each do |json_vuln|
|
||||
# @vulnerabilities << Vulnerability.load_from_json(json_vuln)
|
||||
#end
|
||||
[*db_data['vulnerabilities']].each do |json_vuln|
|
||||
@vulnerabilities << Vulnerability.load_from_json(json_vuln)
|
||||
end
|
||||
|
||||
@vulnerabilities
|
||||
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,7 +5,7 @@ require 'wpscan'
|
||||
|
||||
WPScan::Scan.new do |s|
|
||||
s.controllers <<
|
||||
WPScan::Controller::ApiToken.new <<
|
||||
WPScan::Controller::VulnApi.new <<
|
||||
WPScan::Controller::CustomDirectories.new <<
|
||||
WPScan::Controller::InterestingFindings.new <<
|
||||
WPScan::Controller::WpVersion.new <<
|
||||
|
||||
@@ -7,6 +7,7 @@ require 'wpscan'
|
||||
report = MemoryProfiler.report(top: 15) do
|
||||
WPScan::Scan.new do |s|
|
||||
s.controllers <<
|
||||
WPScan::Controller::VulnApi.new <<
|
||||
WPScan::Controller::CustomDirectories.new <<
|
||||
WPScan::Controller::InterestingFindings.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
|
||||
WPScan::Scan.new do |s|
|
||||
s.controllers <<
|
||||
WPScan::Controller::VulnApi.new <<
|
||||
WPScan::Controller::CustomDirectories.new <<
|
||||
WPScan::Controller::InterestingFindings.new <<
|
||||
WPScan::Controller::WpVersion.new <<
|
||||
|
||||
@@ -13,7 +13,8 @@ require 'uri'
|
||||
require 'time'
|
||||
require 'readline'
|
||||
require 'securerandom'
|
||||
|
||||
# Monkey Patches/Fixes/Override
|
||||
require 'wpscan/typhoeus/response' # Adds a from_vuln_api? method
|
||||
# Custom Libs
|
||||
require 'wpscan/helper'
|
||||
require 'wpscan/db'
|
||||
@@ -38,12 +39,28 @@ module WPScan
|
||||
APP_DIR = Pathname.new(__FILE__).dirname.join('..', 'app').expand_path
|
||||
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'
|
||||
#
|
||||
# @return [ String ]
|
||||
def self.app_name
|
||||
'wpscan'
|
||||
end
|
||||
|
||||
# @return [ Integer ]
|
||||
def self.api_requests
|
||||
@@api_requests ||= 0
|
||||
end
|
||||
|
||||
# @param [ Integer ] value
|
||||
def self.api_requests=(value)
|
||||
@@api_requests = value
|
||||
end
|
||||
end
|
||||
|
||||
require "#{WPScan::APP_DIR}/app"
|
||||
|
||||
@@ -7,7 +7,7 @@ module WPScan
|
||||
|
||||
# @return [ String ]
|
||||
def default_user_agent
|
||||
"WPScan v#{VERSION} (https://wpscan.org/)"
|
||||
@default_user_agent ||= "WPScan v#{VERSION} (https://wpscan.org/)"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,6 +10,8 @@ require_relative 'db/theme'
|
||||
require_relative 'db/wp_version'
|
||||
require_relative 'db/fingerprints'
|
||||
|
||||
require_relative 'db/vuln_api'
|
||||
|
||||
require_relative 'db/dynamic_finders/base'
|
||||
require_relative 'db/dynamic_finders/plugin'
|
||||
require_relative 'db/dynamic_finders/theme'
|
||||
|
||||
@@ -14,7 +14,7 @@ module WPScan
|
||||
|
||||
OLD_FILES = %w[
|
||||
wordpress.db user-agents.txt dynamic_finders_01.yml
|
||||
wordpressess.json plugins.json themes.json
|
||||
wordpresses.json plugins.json themes.json
|
||||
].freeze
|
||||
|
||||
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
|
||||
@@ -12,5 +12,6 @@ end
|
||||
require_relative 'errors/enumeration'
|
||||
require_relative 'errors/http'
|
||||
require_relative 'errors/update'
|
||||
require_relative 'errors/vuln_api'
|
||||
require_relative 'errors/wordpress'
|
||||
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
|
||||
@@ -1,24 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe WPScan::Controller::ApiToken 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
|
||||
xit
|
||||
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
|
||||
|
||||
describe '#latest_version, #last_updated, #popular' do
|
||||
context 'when none' do
|
||||
let(:slug) { 'vulnerable-not-popular' }
|
||||
before { allow(plugin).to receive(:db_data).and_return(db_data) }
|
||||
|
||||
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 values' do
|
||||
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('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
|
||||
|
||||
describe '#outdated?' do
|
||||
before { allow(plugin).to receive(:db_data).and_return({}) }
|
||||
|
||||
context 'when last_version' do
|
||||
let(:slug) { 'no-vulns-popular' }
|
||||
|
||||
@@ -116,13 +131,13 @@ describe WPScan::Model::Plugin do
|
||||
.and_return(WPScan::Model::Version.new(version_number))
|
||||
end
|
||||
|
||||
context 'when version < last_version' do
|
||||
context 'when version < latest_version' do
|
||||
let(:version_number) { '1.2' }
|
||||
|
||||
its(:outdated?) { should eql true }
|
||||
end
|
||||
|
||||
context 'when version >= last_version' do
|
||||
context 'when version >= latest_version' do
|
||||
let(:version_number) { '3.0' }
|
||||
|
||||
its(:outdated?) { should eql false }
|
||||
@@ -130,7 +145,7 @@ describe WPScan::Model::Plugin do
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no last_version' do
|
||||
context 'when no latest_version' do
|
||||
let(:slug) { 'vulnerable-not-popular' }
|
||||
|
||||
context 'when no version' do
|
||||
@@ -153,13 +168,16 @@ describe WPScan::Model::Plugin do
|
||||
end
|
||||
|
||||
describe '#vulnerabilities' do
|
||||
before { allow(plugin).to receive(:db_data).and_return(db_data) }
|
||||
|
||||
after do
|
||||
expect(plugin.vulnerabilities).to eq @expected
|
||||
expect(plugin.vulnerable?).to eql @expected.empty? ? false : true
|
||||
end
|
||||
|
||||
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
|
||||
@expected = []
|
||||
@@ -168,7 +186,8 @@ describe WPScan::Model::Plugin do
|
||||
|
||||
context 'when in the DB' 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
|
||||
@expected = []
|
||||
@@ -176,11 +195,13 @@ describe WPScan::Model::Plugin do
|
||||
end
|
||||
|
||||
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
|
||||
[
|
||||
WPScan::Vulnerability.new(
|
||||
'First Vuln',
|
||||
'First Vuln <= 6.3.10 - LFI',
|
||||
{ wpvulndb: '1' },
|
||||
'LFI',
|
||||
'6.3.10'
|
||||
|
||||
@@ -86,8 +86,179 @@ describe WPScan::Model::Theme do
|
||||
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
|
||||
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
|
||||
|
||||
describe '#parent_theme' do
|
||||
|
||||
@@ -40,11 +40,13 @@ describe WPScan::Model::WpVersion do
|
||||
|
||||
describe '#vulnerabilities' do
|
||||
subject(:version) { described_class.new(number) }
|
||||
before { allow(version).to receive(:db_data).and_return(db_data) }
|
||||
|
||||
context 'when no vulns' do
|
||||
let(:number) { '4.4' }
|
||||
let(:db_data) { { 'vulnerabilities' => [] } }
|
||||
|
||||
its(:vulnerabilities) { should eql([]) }
|
||||
its(:vulnerabilities) { should be_empty }
|
||||
end
|
||||
|
||||
context 'when vulnerable' do
|
||||
@@ -53,8 +55,25 @@ describe WPScan::Model::WpVersion do
|
||||
expect(version).to be_vulnerable
|
||||
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
|
||||
let(:number) { '3.8' }
|
||||
let(:number) { '3.8.1' }
|
||||
let(:db_data) { vuln_api_data_for('wordpresses/38') }
|
||||
|
||||
it 'returns the expected result' do
|
||||
@expected = [WPScan::Vulnerability.new(
|
||||
@@ -67,6 +86,7 @@ describe WPScan::Model::WpVersion do
|
||||
|
||||
context 'when multiple vulns' do
|
||||
let(:number) { '3.8.1' }
|
||||
let(:db_data) { vuln_api_data_for('wordpresses/381') }
|
||||
|
||||
it 'returns the expected results' do
|
||||
@expected = [
|
||||
@@ -87,27 +107,30 @@ describe WPScan::Model::WpVersion do
|
||||
end
|
||||
end
|
||||
|
||||
describe '#release_date' do
|
||||
describe '#metadata, #release_date, #status' do
|
||||
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
|
||||
subject(:version) { described_class.new('3.8.2') }
|
||||
context 'when no db_data' do
|
||||
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
|
||||
|
||||
describe '#status' do
|
||||
subject(:version) { described_class.new('3.8.1') }
|
||||
context 'when db_data' do
|
||||
let(:db_data) { vuln_api_data_for('wordpresses/381') }
|
||||
|
||||
its(:status) { should eql 'outdated' }
|
||||
|
||||
context 'when the version is not in the DB' do
|
||||
subject(:version) { described_class.new('3.8.2') }
|
||||
|
||||
its(:release_date) { should eql 'Unknown' }
|
||||
its(:release_date) { should eql '2014-01-23-via-api' }
|
||||
its(:status) { should eql 'outdated-via-api' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,6 +9,7 @@ describe 'App::Views' do
|
||||
# in the expected output.
|
||||
%i[JSON CliNoColour].each do |formatter|
|
||||
context "when #{formatter}" do
|
||||
it_behaves_like 'App::Views::VulnApi'
|
||||
it_behaves_like 'App::Views::WpVersion'
|
||||
it_behaves_like 'App::Views::MainTheme'
|
||||
it_behaves_like 'App::Views::Enumeration'
|
||||
|
||||
6
spec/fixtures/db/metadata.json
vendored
6
spec/fixtures/db/metadata.json
vendored
@@ -34,6 +34,12 @@
|
||||
"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,
|
||||
|
||||
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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
48
spec/fixtures/db/themes.json
vendored
48
spec/fixtures/db/themes.json
vendored
@@ -1,48 +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",
|
||||
"http://packetstormsecurity.com/files/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",
|
||||
"http://packetstormsecurity.com/files/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"
|
||||
}
|
||||
50
spec/fixtures/db/wordpresses.json
vendored
50
spec/fixtures/db/wordpresses.json
vendored
@@ -1,50 +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"],
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
"3.8": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ describe WPScan::DB::Themes do
|
||||
subject(:themes) { described_class }
|
||||
|
||||
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
|
||||
|
||||
describe '#popular_slugs' do
|
||||
@@ -12,6 +12,6 @@ describe WPScan::DB::Themes do
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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 }
|
||||
end
|
||||
|
||||
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 }
|
||||
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
|
||||
|
|
||||
| [!] 2 vulnerabilities identified:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"version": {
|
||||
"number": "3.8.1",
|
||||
"release_date": "2014-01-23",
|
||||
"status": "outdated",
|
||||
"release_date": "2014-01-23-via-api",
|
||||
"status": "outdated-via-api",
|
||||
"found_by": "rspec",
|
||||
"confidence": 0,
|
||||
"interesting_entries": [
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'shared_examples/views/vuln_api'
|
||||
require 'shared_examples/views/wp_version'
|
||||
require 'shared_examples/views/main_theme'
|
||||
require 'shared_examples/views/enumeration'
|
||||
|
||||
@@ -60,6 +60,8 @@ shared_examples 'App::Views::MainTheme' do
|
||||
|
||||
it 'outputs the expected string' do
|
||||
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)
|
||||
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
|
||||
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 no interesting_entries' do
|
||||
@@ -77,9 +78,12 @@ shared_examples 'App::Views::WpVersion' do
|
||||
|
||||
context 'when the version is vulnerable' do
|
||||
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
|
||||
@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
|
||||
|
||||
@@ -44,6 +44,10 @@ def df_stubbed_response(fixture, finder_super_class)
|
||||
end
|
||||
end
|
||||
|
||||
def vuln_api_data_for(path)
|
||||
JSON.parse(File.read(FIXTURES.join('db', 'vuln_api', "#{path}.json")))
|
||||
end
|
||||
|
||||
require 'wpscan'
|
||||
require 'shared_examples'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user