Compare commits
3 Commits
v3.7.0
...
plugin-bac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82db02a688 | ||
|
|
2c07de8c6b | ||
|
|
4b0b8fa624 |
11
.rubocop.yml
11
.rubocop.yml
@@ -8,12 +8,10 @@ ClassVars:
|
|||||||
Enabled: false
|
Enabled: false
|
||||||
LineLength:
|
LineLength:
|
||||||
Max: 120
|
Max: 120
|
||||||
Lint/UriEscapeUnescape:
|
|
||||||
Enabled: false
|
|
||||||
MethodLength:
|
MethodLength:
|
||||||
Max: 20
|
Max: 20
|
||||||
Exclude:
|
Lint/UriEscapeUnescape:
|
||||||
- 'app/controllers/enumeration/cli_options.rb'
|
Enabled: false
|
||||||
Metrics/AbcSize:
|
Metrics/AbcSize:
|
||||||
Max: 25
|
Max: 25
|
||||||
Metrics/BlockLength:
|
Metrics/BlockLength:
|
||||||
@@ -21,14 +19,9 @@ Metrics/BlockLength:
|
|||||||
- 'spec/**/*'
|
- 'spec/**/*'
|
||||||
Metrics/ClassLength:
|
Metrics/ClassLength:
|
||||||
Max: 150
|
Max: 150
|
||||||
Exclude:
|
|
||||||
- 'app/controllers/enumeration/cli_options.rb'
|
|
||||||
Metrics/CyclomaticComplexity:
|
Metrics/CyclomaticComplexity:
|
||||||
Max: 8
|
Max: 8
|
||||||
Style/Documentation:
|
Style/Documentation:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
Style/FormatStringToken:
|
Style/FormatStringToken:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
Style/NumericPredicate:
|
|
||||||
Exclude:
|
|
||||||
- 'app/controllers/vuln_api.rb'
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM ruby:2.6.3-alpine AS builder
|
FROM ruby:2.6.2-alpine3.9 AS builder
|
||||||
LABEL maintainer="WPScan Team <team@wpscan.org>"
|
LABEL maintainer="WPScan Team <team@wpscan.org>"
|
||||||
|
|
||||||
ARG BUNDLER_ARGS="--jobs=8 --without test development"
|
ARG BUNDLER_ARGS="--jobs=8 --without test development"
|
||||||
@@ -19,7 +19,7 @@ RUN rake install --trace
|
|||||||
RUN chmod -R a+r /usr/local/bundle
|
RUN chmod -R a+r /usr/local/bundle
|
||||||
|
|
||||||
|
|
||||||
FROM ruby:2.6.3-alpine
|
FROM ruby:2.6.2-alpine3.9
|
||||||
LABEL maintainer="WPScan Team <team@wpscan.org>"
|
LABEL maintainer="WPScan Team <team@wpscan.org>"
|
||||||
|
|
||||||
RUN adduser -h /wpscan -g WPScan -D wpscan
|
RUN adduser -h /wpscan -g WPScan -D wpscan
|
||||||
|
|||||||
37
README.md
37
README.md
@@ -17,6 +17,7 @@
|
|||||||
<a href="https://badge.fury.io/rb/wpscan" target="_blank"><img src="https://badge.fury.io/rb/wpscan.svg"></a>
|
<a href="https://badge.fury.io/rb/wpscan" target="_blank"><img src="https://badge.fury.io/rb/wpscan.svg"></a>
|
||||||
<a href="https://travis-ci.org/wpscanteam/wpscan" target="_blank"><img src="https://travis-ci.org/wpscanteam/wpscan.svg?branch=master"></a>
|
<a href="https://travis-ci.org/wpscanteam/wpscan" target="_blank"><img src="https://travis-ci.org/wpscanteam/wpscan.svg?branch=master"></a>
|
||||||
<a href="https://codeclimate.com/github/wpscanteam/wpscan" target="_blank"><img src="https://codeclimate.com/github/wpscanteam/wpscan/badges/gpa.svg"></a>
|
<a href="https://codeclimate.com/github/wpscanteam/wpscan" target="_blank"><img src="https://codeclimate.com/github/wpscanteam/wpscan/badges/gpa.svg"></a>
|
||||||
|
<a href="https://www.patreon.com/wpscan" target="_blank"><img src="https://img.shields.io/badge/patreon-donate-green.svg"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
# INSTALL
|
# INSTALL
|
||||||
@@ -29,7 +30,6 @@
|
|||||||
- Curl >= 7.21 - Recommended: latest
|
- Curl >= 7.21 - Recommended: latest
|
||||||
- The 7.29 has a segfault
|
- The 7.29 has a segfault
|
||||||
- RubyGems - Recommended: latest
|
- RubyGems - Recommended: latest
|
||||||
- Nokogiri might require packages to be installed via your package manager depending on your OS, see https://nokogiri.org/tutorials/installing_nokogiri.html
|
|
||||||
|
|
||||||
### From RubyGems (Recommended)
|
### From RubyGems (Recommended)
|
||||||
|
|
||||||
@@ -84,46 +84,33 @@ For more options, open a terminal and type ```wpscan --help``` (if you built wps
|
|||||||
|
|
||||||
The DB is located at ~/.wpscan/db
|
The DB is located at ~/.wpscan/db
|
||||||
|
|
||||||
## Load CLI options from file/s
|
|
||||||
|
|
||||||
WPScan can load all options (including the --url) from configuration files, the following locations are checked (order: first to last):
|
WPScan can load all options (including the --url) from configuration files, the following locations are checked (order: first to last):
|
||||||
|
|
||||||
- ~/.wpscan/scan.json
|
- ~/.wpscan/cli_options.json
|
||||||
- ~/.wpscan/scan.yml
|
- ~/.wpscan/cli_options.yml
|
||||||
- pwd/.wpscan/scan.json
|
- pwd/.wpscan/cli_options.json
|
||||||
- pwd/.wpscan/scan.yml
|
- pwd/.wpscan/cli_options.yml
|
||||||
|
|
||||||
If those files exist, options from the `cli_options` key will be loaded and overridden if found twice.
|
If those files exist, options from them will be loaded and overridden if found twice.
|
||||||
|
|
||||||
e.g:
|
e.g:
|
||||||
|
|
||||||
~/.wpscan/scan.yml:
|
~/.wpscan/cli_options.yml:
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
cli_options:
|
proxy: 'http://127.0.0.1:8080'
|
||||||
proxy: 'http://127.0.0.1:8080'
|
verbose: true
|
||||||
verbose: true
|
|
||||||
```
|
```
|
||||||
|
|
||||||
pwd/.wpscan/scan.yml:
|
pwd/.wpscan/cli_options.yml:
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
cli_options:
|
proxy: 'socks5://127.0.0.1:9090'
|
||||||
proxy: 'socks5://127.0.0.1:9090'
|
url: 'http://target.tld'
|
||||||
url: 'http://target.tld'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Running ```wpscan``` in the current directory (pwd), is the same as ```wpscan -v --proxy socks5://127.0.0.1:9090 --url http://target.tld```
|
Running ```wpscan``` in the current directory (pwd), is the same as ```wpscan -v --proxy socks5://127.0.0.1:9090 --url http://target.tld```
|
||||||
|
|
||||||
## Save API Token in a file
|
|
||||||
|
|
||||||
The feature mentioned above is useful to keep the API Token in a config file and not have to supply it via the CLI each time. To do so, create the ~/.wpscan/scan.yml file containing the below:
|
|
||||||
|
|
||||||
```yml
|
|
||||||
cli_options:
|
|
||||||
api_token: YOUR_API_TOKEN
|
|
||||||
```
|
|
||||||
|
|
||||||
Enumerating usernames
|
Enumerating usernames
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# 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'
|
||||||
|
|||||||
@@ -7,6 +7,15 @@ module WPScan
|
|||||||
module Controller
|
module Controller
|
||||||
# Enumeration Controller
|
# Enumeration Controller
|
||||||
class Enumeration < CMSScanner::Controller::Base
|
class Enumeration < CMSScanner::Controller::Base
|
||||||
|
def before_scan
|
||||||
|
DB::DynamicFinders::Plugin.create_versions_finders
|
||||||
|
DB::DynamicFinders::Theme.create_versions_finders
|
||||||
|
|
||||||
|
# Force the Garbage Collector to run due to the above method being
|
||||||
|
# quite heavy in objects allocation
|
||||||
|
GC.start
|
||||||
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
enum = ParsedCli.enumerate || {}
|
enum = ParsedCli.enumerate || {}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ module WPScan
|
|||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Array<OptParseValidator::OptBase> ]
|
# @return [ Array<OptParseValidator::OptBase> ]
|
||||||
|
# rubocop:disable Metrics/MethodLength
|
||||||
def cli_enum_choices
|
def cli_enum_choices
|
||||||
[
|
[
|
||||||
OptMultiChoices.new(
|
OptMultiChoices.new(
|
||||||
@@ -44,6 +45,7 @@ module WPScan
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
# rubocop:enable Metrics/MethodLength
|
||||||
|
|
||||||
# @return [ Array<OptParseValidator::OptBase> ]
|
# @return [ Array<OptParseValidator::OptBase> ]
|
||||||
def cli_plugins_opts
|
def cli_plugins_opts
|
||||||
@@ -65,11 +67,6 @@ module WPScan
|
|||||||
'Use the supplied mode to check plugins versions instead of the --detection-mode ' \
|
'Use the supplied mode to check plugins versions instead of the --detection-mode ' \
|
||||||
'or --plugins-detection modes.'],
|
'or --plugins-detection modes.'],
|
||||||
choices: %w[mixed passive aggressive], normalize: :to_sym, default: :mixed
|
choices: %w[mixed passive aggressive], normalize: :to_sym, default: :mixed
|
||||||
),
|
|
||||||
OptInteger.new(
|
|
||||||
['--plugins-threshold THRESHOLD',
|
|
||||||
'Raise an error when the number of detected plugins via known locations reaches the threshold. ' \
|
|
||||||
'Set to 0 to ignore the threshold.'], default: 100
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
@@ -94,11 +91,6 @@ module WPScan
|
|||||||
'Use the supplied mode to check themes versions instead of the --detection-mode ' \
|
'Use the supplied mode to check themes versions instead of the --detection-mode ' \
|
||||||
'or --themes-detection modes.'],
|
'or --themes-detection modes.'],
|
||||||
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
||||||
),
|
|
||||||
OptInteger.new(
|
|
||||||
['--themes-threshold THRESHOLD',
|
|
||||||
'Raise an error when the number of detected themes via known locations reaches the threshold. ' \
|
|
||||||
'Set to 0 to ignore the threshold.'], default: 20
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ module WPScan
|
|||||||
def enum_plugins
|
def enum_plugins
|
||||||
opts = default_opts('plugins').merge(
|
opts = default_opts('plugins').merge(
|
||||||
list: plugins_list_from_opts(ParsedCli.options),
|
list: plugins_list_from_opts(ParsedCli.options),
|
||||||
threshold: ParsedCli.plugins_threshold,
|
|
||||||
sort: true
|
sort: true
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -109,7 +108,6 @@ module WPScan
|
|||||||
def enum_themes
|
def enum_themes
|
||||||
opts = default_opts('themes').merge(
|
opts = default_opts('themes').merge(
|
||||||
list: themes_list_from_opts(ParsedCli.options),
|
list: themes_list_from_opts(ParsedCli.options),
|
||||||
threshold: ParsedCli.themes_threshold,
|
|
||||||
sort: true
|
sort: true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -65,43 +65,30 @@ module WPScan
|
|||||||
|
|
||||||
case ParsedCli.password_attack
|
case ParsedCli.password_attack
|
||||||
when :wp_login
|
when :wp_login
|
||||||
Finders::Passwords::WpLogin.new(target)
|
WPScan::Finders::Passwords::WpLogin.new(target)
|
||||||
when :xmlrpc
|
when :xmlrpc
|
||||||
raise Error::XMLRPCNotDetected unless xmlrpc
|
raise Error::XMLRPCNotDetected unless xmlrpc
|
||||||
|
|
||||||
Finders::Passwords::XMLRPC.new(xmlrpc)
|
WPScan::Finders::Passwords::XMLRPC.new(xmlrpc)
|
||||||
when :xmlrpc_multicall
|
when :xmlrpc_multicall
|
||||||
raise Error::XMLRPCNotDetected unless xmlrpc
|
raise Error::XMLRPCNotDetected unless xmlrpc
|
||||||
|
|
||||||
Finders::Passwords::XMLRPCMulticall.new(xmlrpc)
|
WPScan::Finders::Passwords::XMLRPCMulticall.new(xmlrpc)
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [ Boolean ]
|
|
||||||
def xmlrpc_get_users_blogs_enabled?
|
|
||||||
if xmlrpc&.enabled? &&
|
|
||||||
xmlrpc.available_methods.include?('wp.getUsersBlogs') &&
|
|
||||||
xmlrpc.method_call('wp.getUsersBlogs', [SecureRandom.hex[0, 6], SecureRandom.hex[0, 4]])
|
|
||||||
.run.body !~ /XML\-RPC services are disabled/
|
|
||||||
|
|
||||||
true
|
|
||||||
else
|
|
||||||
false
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ CMSScanner::Finders::Finder ]
|
# @return [ CMSScanner::Finders::Finder ]
|
||||||
def attacker_from_automatic_detection
|
def attacker_from_automatic_detection
|
||||||
if xmlrpc_get_users_blogs_enabled?
|
if xmlrpc&.enabled? && xmlrpc.available_methods.include?('wp.getUsersBlogs')
|
||||||
wp_version = target.wp_version
|
wp_version = target.wp_version
|
||||||
|
|
||||||
if wp_version && wp_version < '4.4'
|
if wp_version && wp_version < '4.4'
|
||||||
Finders::Passwords::XMLRPCMulticall.new(xmlrpc)
|
WPScan::Finders::Passwords::XMLRPCMulticall.new(xmlrpc)
|
||||||
else
|
else
|
||||||
Finders::Passwords::XMLRPC.new(xmlrpc)
|
WPScan::Finders::Passwords::XMLRPC.new(xmlrpc)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
Finders::Passwords::WpLogin.new(target)
|
WPScan::Finders::Passwords::WpLogin.new(target)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
# 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
|
|
||||||
@@ -17,7 +17,7 @@ module WPScan
|
|||||||
end
|
end
|
||||||
|
|
||||||
def before_scan
|
def before_scan
|
||||||
DB::DynamicFinders::Wordpress.create_versions_finders
|
WPScan::DB::DynamicFinders::Wordpress.create_versions_finders
|
||||||
end
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ module WPScan
|
|||||||
|
|
||||||
enumerate(potential_urls(opts), opts.merge(check_full_response: 200)) do |res|
|
enumerate(potential_urls(opts), opts.merge(check_full_response: 200)) do |res|
|
||||||
if res.effective_url.end_with?('.zip')
|
if res.effective_url.end_with?('.zip')
|
||||||
next unless %r{\Aapplication/zip}i.match?(res.headers['Content-Type'])
|
next unless res.headers['Content-Type'] =~ %r{\Aapplication/zip}i
|
||||||
else
|
else
|
||||||
next unless SQL_PATTERN.match?(res.body)
|
next unless res.body =~ SQL_PATTERN
|
||||||
end
|
end
|
||||||
|
|
||||||
found << Model::DbExport.new(res.request.url, found_by: DIRECT_ACCESS, confidence: 100)
|
found << Model::DbExport.new(res.request.url, found_by: DIRECT_ACCESS, confidence: 100)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ require_relative 'interesting_findings/readme'
|
|||||||
require_relative 'interesting_findings/wp_cron'
|
require_relative 'interesting_findings/wp_cron'
|
||||||
require_relative 'interesting_findings/multisite'
|
require_relative 'interesting_findings/multisite'
|
||||||
require_relative 'interesting_findings/debug_log'
|
require_relative 'interesting_findings/debug_log'
|
||||||
require_relative 'interesting_findings/backup_db'
|
require_relative 'interesting_findings/plugin_backup_folders'
|
||||||
require_relative 'interesting_findings/mu_plugins'
|
require_relative 'interesting_findings/mu_plugins'
|
||||||
require_relative 'interesting_findings/registration'
|
require_relative 'interesting_findings/registration'
|
||||||
require_relative 'interesting_findings/tmm_db_migrate'
|
require_relative 'interesting_findings/tmm_db_migrate'
|
||||||
@@ -24,7 +24,7 @@ module WPScan
|
|||||||
super(target)
|
super(target)
|
||||||
|
|
||||||
%w[
|
%w[
|
||||||
Readme DebugLog FullPathDisclosure BackupDB DuplicatorInstallerLog
|
Readme DebugLog FullPathDisclosure PluginBackupFolders DuplicatorInstallerLog
|
||||||
Multisite MuPlugins Registration UploadDirectoryListing TmmDbMigrate
|
Multisite MuPlugins Registration UploadDirectoryListing TmmDbMigrate
|
||||||
UploadSQLDump EmergencyPwdResetScript WPCron
|
UploadSQLDump EmergencyPwdResetScript WPCron
|
||||||
].each do |f|
|
].each do |f|
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module WPScan
|
|
||||||
module Finders
|
|
||||||
module InterestingFindings
|
|
||||||
# BackupDB finder
|
|
||||||
class BackupDB < CMSScanner::Finders::Finder
|
|
||||||
# @return [ InterestingFinding ]
|
|
||||||
def aggressive(_opts = {})
|
|
||||||
path = 'wp-content/backup-db/'
|
|
||||||
res = target.head_and_get(path, [200, 403])
|
|
||||||
|
|
||||||
return unless [200, 403].include?(res.code) && !target.homepage_or_404?(res)
|
|
||||||
|
|
||||||
Model::BackupDB.new(
|
|
||||||
target.url(path),
|
|
||||||
confidence: 70,
|
|
||||||
found_by: DIRECT_ACCESS,
|
|
||||||
interesting_entries: target.directory_listing_entries(path),
|
|
||||||
references: { url: 'https://github.com/wpscanteam/wpscan/issues/422' }
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -9,7 +9,7 @@ module WPScan
|
|||||||
def aggressive(_opts = {})
|
def aggressive(_opts = {})
|
||||||
path = 'installer-log.txt'
|
path = 'installer-log.txt'
|
||||||
|
|
||||||
return unless /DUPLICATOR INSTALL-LOG/.match?(target.head_and_get(path).body)
|
return unless target.head_and_get(path).body =~ /DUPLICATOR INSTALL-LOG/
|
||||||
|
|
||||||
Model::DuplicatorInstallerLog.new(
|
Model::DuplicatorInstallerLog.new(
|
||||||
target.url(path),
|
target.url(path),
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ module WPScan
|
|||||||
pattern = %r{#{target.content_dir}/mu\-plugins/}i
|
pattern = %r{#{target.content_dir}/mu\-plugins/}i
|
||||||
|
|
||||||
target.in_scope_uris(target.homepage_res) do |uri|
|
target.in_scope_uris(target.homepage_res) do |uri|
|
||||||
next unless uri.path&.match?(pattern)
|
next unless uri.path =~ pattern
|
||||||
|
|
||||||
url = target.url('wp-content/mu-plugins/')
|
url = target.url('wp-content/mu-plugins/')
|
||||||
|
|
||||||
|
|||||||
34
app/finders/interesting_findings/plugin_backup_folders.rb
Normal file
34
app/finders/interesting_findings/plugin_backup_folders.rb
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module WPScan
|
||||||
|
module Finders
|
||||||
|
module InterestingFindings
|
||||||
|
# Known Backup Folders from Plugin finder
|
||||||
|
class PluginBackupFolders < CMSScanner::Finders::Finder
|
||||||
|
PATHS = %w[wp-content/backup-db/ wp-content/backups-dup-pro/ wp-content/updraft/].freeze
|
||||||
|
|
||||||
|
# @return [ InterestingFinding ]
|
||||||
|
def aggressive(_opts = {})
|
||||||
|
found = []
|
||||||
|
|
||||||
|
PATHS.each do |path|
|
||||||
|
res = target.head_and_get(path, [200, 403])
|
||||||
|
|
||||||
|
next unless [200, 403].include?(res.code) && !target.homepage_or_404?(res)
|
||||||
|
|
||||||
|
found << Model::PluginBackupFolder.new(
|
||||||
|
target.url(path),
|
||||||
|
confidence: 70,
|
||||||
|
found_by: DIRECT_ACCESS,
|
||||||
|
interesting_entries: target.directory_listing_entries(path),
|
||||||
|
references: { url: ['https://github.com/wpscanteam/wpscan/issues/422',
|
||||||
|
'https://github.com/wpscanteam/wpscan/issues/1342'] }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -12,7 +12,7 @@ module WPScan
|
|||||||
path = 'wp-content/uploads/dump.sql'
|
path = 'wp-content/uploads/dump.sql'
|
||||||
res = target.head_and_get(path, [200], get: { headers: { 'Range' => 'bytes=0-3000' } })
|
res = target.head_and_get(path, [200], get: { headers: { 'Range' => 'bytes=0-3000' } })
|
||||||
|
|
||||||
return unless SQL_PATTERN.match?(res.body)
|
return unless res.body =~ SQL_PATTERN
|
||||||
|
|
||||||
Model::UploadSQLDump.new(
|
Model::UploadSQLDump.new(
|
||||||
target.url(path),
|
target.url(path),
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ module WPScan
|
|||||||
|
|
||||||
def valid_credentials?(response)
|
def valid_credentials?(response)
|
||||||
response.code == 302 &&
|
response.code == 302 &&
|
||||||
[*response.headers['Set-Cookie']]&.any? { |cookie| cookie =~ /wordpress_logged_in_/i }
|
response.headers['Set-Cookie']&.any? { |cookie| cookie =~ /wordpress_logged_in_/i }
|
||||||
end
|
end
|
||||||
|
|
||||||
def errored_response?(response)
|
def errored_response?(response)
|
||||||
|
|||||||
@@ -13,15 +13,25 @@ module WPScan
|
|||||||
def initialize(plugin)
|
def initialize(plugin)
|
||||||
finders << PluginVersion::Readme.new(plugin)
|
finders << PluginVersion::Readme.new(plugin)
|
||||||
|
|
||||||
create_and_load_dynamic_versions_finders(plugin)
|
load_specific_finders(plugin)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Create the dynamic version finders related to the plugin and register them
|
# Load the finders associated with the plugin
|
||||||
#
|
#
|
||||||
# @param [ Model::Plugin ] plugin
|
# @param [ Model::Plugin ] plugin
|
||||||
def create_and_load_dynamic_versions_finders(plugin)
|
def load_specific_finders(plugin)
|
||||||
DB::DynamicFinders::Plugin.create_versions_finders(plugin.slug).each do |finder|
|
module_name = plugin.classify
|
||||||
finders << finder.new(plugin)
|
|
||||||
|
return unless Finders::PluginVersion.constants.include?(module_name)
|
||||||
|
|
||||||
|
mod = Finders::PluginVersion.const_get(module_name)
|
||||||
|
|
||||||
|
mod.constants.each do |constant|
|
||||||
|
c = mod.const_get(constant)
|
||||||
|
|
||||||
|
next unless c.is_a?(Class)
|
||||||
|
|
||||||
|
finders << c.new(plugin)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ module WPScan
|
|||||||
|
|
||||||
# The target(plugin)#readme_url can't be used directly here
|
# The target(plugin)#readme_url can't be used directly here
|
||||||
# as if the --detection-mode is passive, it will always return nil
|
# as if the --detection-mode is passive, it will always return nil
|
||||||
target.potential_readme_filenames.each do |file|
|
Model::WpItem::READMES.each do |file|
|
||||||
res = target.head_and_get(file)
|
res = target.head_and_get(file)
|
||||||
|
|
||||||
next unless res.code == 200 && !(numbers = version_numbers(res.body)).empty?
|
next unless res.code == 200 && !(numbers = version_numbers(res.body)).empty?
|
||||||
@@ -52,7 +52,7 @@ module WPScan
|
|||||||
|
|
||||||
number = Regexp.last_match[1]
|
number = Regexp.last_match[1]
|
||||||
|
|
||||||
number if /[0-9]+/.match?(number)
|
number if number =~ /[0-9]+/
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ String ] body
|
# @param [ String ] body
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ module WPScan
|
|||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
# Plugins finder from Dynamic Finder 'BodyPattern'
|
# Plugins finder from Dynamic Finder 'BodyPattern'
|
||||||
class BodyPattern < Finders::DynamicFinder::WpItems::Finder
|
class BodyPattern < WPScan::Finders::DynamicFinder::WpItems::Finder
|
||||||
DEFAULT_CONFIDENCE = 30
|
DEFAULT_CONFIDENCE = 30
|
||||||
|
|
||||||
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
||||||
@@ -15,7 +15,7 @@ module WPScan
|
|||||||
#
|
#
|
||||||
# @return [ Plugin ] The detected plugin in the response, related to the config
|
# @return [ Plugin ] The detected plugin in the response, related to the config
|
||||||
def process_response(opts, response, slug, klass, config)
|
def process_response(opts, response, slug, klass, config)
|
||||||
return unless response.body&.match?(config['pattern'])
|
return unless response.body =~ config['pattern']
|
||||||
|
|
||||||
Model::Plugin.new(
|
Model::Plugin.new(
|
||||||
slug,
|
slug,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ module WPScan
|
|||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
# Plugins finder from the Dynamic Finder 'Comment'
|
# Plugins finder from the Dynamic Finder 'Comment'
|
||||||
class Comment < Finders::DynamicFinder::WpItems::Finder
|
class Comment < WPScan::Finders::DynamicFinder::WpItems::Finder
|
||||||
DEFAULT_CONFIDENCE = 30
|
DEFAULT_CONFIDENCE = 30
|
||||||
|
|
||||||
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
||||||
@@ -18,7 +18,7 @@ module WPScan
|
|||||||
response.html.xpath(config['xpath'] || '//comment()').each do |node|
|
response.html.xpath(config['xpath'] || '//comment()').each do |node|
|
||||||
comment = node.text.to_s.strip
|
comment = node.text.to_s.strip
|
||||||
|
|
||||||
next unless comment&.match?(config['pattern'])
|
next unless comment =~ config['pattern']
|
||||||
|
|
||||||
return Model::Plugin.new(
|
return Model::Plugin.new(
|
||||||
slug,
|
slug,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ module WPScan
|
|||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
# Plugins finder from Dynamic Finder 'ConfigParser'
|
# Plugins finder from Dynamic Finder 'ConfigParser'
|
||||||
class ConfigParser < Finders::DynamicFinder::WpItems::Finder
|
class ConfigParser < WPScan::Finders::DynamicFinder::WpItems::Finder
|
||||||
DEFAULT_CONFIDENCE = 40
|
DEFAULT_CONFIDENCE = 40
|
||||||
|
|
||||||
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ module WPScan
|
|||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
# Plugins finder from Dynamic Finder 'HeaderPattern'
|
# Plugins finder from Dynamic Finder 'HeaderPattern'
|
||||||
class HeaderPattern < Finders::DynamicFinder::WpItems::Finder
|
class HeaderPattern < WPScan::Finders::DynamicFinder::WpItems::Finder
|
||||||
DEFAULT_CONFIDENCE = 30
|
DEFAULT_CONFIDENCE = 30
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ module WPScan
|
|||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
# Plugins finder from the Dynamic Finder 'JavascriptVar'
|
# Plugins finder from the Dynamic Finder 'JavascriptVar'
|
||||||
class JavascriptVar < Finders::DynamicFinder::WpItems::Finder
|
class JavascriptVar < WPScan::Finders::DynamicFinder::WpItems::Finder
|
||||||
DEFAULT_CONFIDENCE = 60
|
DEFAULT_CONFIDENCE = 60
|
||||||
|
|
||||||
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
||||||
|
|||||||
@@ -21,8 +21,6 @@ module WPScan
|
|||||||
|
|
||||||
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |_res, slug|
|
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |_res, slug|
|
||||||
found << Model::Plugin.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
found << Model::Plugin.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
||||||
|
|
||||||
raise Error::PluginsThresholdReached if opts[:threshold].positive? && found.size >= opts[:threshold]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ module WPScan
|
|||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
# Plugins finder from Dynamic Finder 'QueryParameter'
|
# Plugins finder from Dynamic Finder 'QueryParameter'
|
||||||
class QueryParameter < Finders::DynamicFinder::WpItems::Finder
|
class QueryParameter < WPScan::Finders::DynamicFinder::WpItems::Finder
|
||||||
DEFAULT_CONFIDENCE = 10
|
DEFAULT_CONFIDENCE = 10
|
||||||
|
|
||||||
def passive(_opts = {})
|
def passive(_opts = {})
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ module WPScan
|
|||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
# Plugins finder from the Dynamic Finder 'Xpath'
|
# Plugins finder from the Dynamic Finder 'Xpath'
|
||||||
class Xpath < Finders::DynamicFinder::WpItems::Finder
|
class Xpath < WPScan::Finders::DynamicFinder::WpItems::Finder
|
||||||
DEFAULT_CONFIDENCE = 40
|
DEFAULT_CONFIDENCE = 40
|
||||||
|
|
||||||
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
||||||
|
|||||||
@@ -16,15 +16,25 @@ module WPScan
|
|||||||
ThemeVersion::Style.new(theme) <<
|
ThemeVersion::Style.new(theme) <<
|
||||||
ThemeVersion::WooFrameworkMetaGenerator.new(theme)
|
ThemeVersion::WooFrameworkMetaGenerator.new(theme)
|
||||||
|
|
||||||
create_and_load_dynamic_versions_finders(theme)
|
load_specific_finders(theme)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Create the dynamic version finders related to the theme and register them
|
# Load the finders associated with the theme
|
||||||
#
|
#
|
||||||
# @param [ Model::Theme ] theme
|
# @param [ Model::Theme ] theme
|
||||||
def create_and_load_dynamic_versions_finders(theme)
|
def load_specific_finders(theme)
|
||||||
DB::DynamicFinders::Theme.create_versions_finders(theme.slug).each do |finder|
|
module_name = theme.classify
|
||||||
finders << finder.new(theme)
|
|
||||||
|
return unless Finders::ThemeVersion.constants.include?(module_name)
|
||||||
|
|
||||||
|
mod = Finders::ThemeVersion.const_get(module_name)
|
||||||
|
|
||||||
|
mod.constants.each do |constant|
|
||||||
|
c = mod.const_get(constant)
|
||||||
|
|
||||||
|
next unless c.is_a?(Class)
|
||||||
|
|
||||||
|
finders << c.new(theme)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -21,8 +21,6 @@ module WPScan
|
|||||||
|
|
||||||
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |_res, slug|
|
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |_res, slug|
|
||||||
found << Model::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
found << Model::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
||||||
|
|
||||||
raise Error::ThemesThresholdReached if opts[:threshold].positive? && found.size >= opts[:threshold]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ module WPScan
|
|||||||
found = []
|
found = []
|
||||||
|
|
||||||
enumerate(target_urls(opts), opts.merge(check_full_response: 400)) do |res|
|
enumerate(target_urls(opts), opts.merge(check_full_response: 400)) do |res|
|
||||||
next unless /no image specified/i.match?(res.body)
|
next unless res.body =~ /no image specified/i
|
||||||
|
|
||||||
found << Model::Timthumb.new(res.request.url, opts.merge(found_by: found_by, confidence: 100))
|
found << Model::Timthumb.new(res.request.url, opts.merge(found_by: found_by, confidence: 100))
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ module WPScan
|
|||||||
|
|
||||||
return found if error.empty? # Protection plugin / error disabled
|
return found if error.empty? # Protection plugin / error disabled
|
||||||
|
|
||||||
next unless /The password you entered for the username|Incorrect Password/i.match?(error)
|
next unless error =~ /The password you entered for the username|Incorrect Password/i
|
||||||
|
|
||||||
found << Model::User.new(username, found_by: found_by, confidence: 100)
|
found << Model::User.new(username, found_by: found_by, confidence: 100)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ module WPScan
|
|||||||
# Users disclosed from the dc:creator field in the RSS
|
# Users disclosed from the dc:creator field in the RSS
|
||||||
# The names disclosed are display names, however depending on the configuration of the blog,
|
# The names disclosed are display names, however depending on the configuration of the blog,
|
||||||
# they can be the same than usernames
|
# they can be the same than usernames
|
||||||
class RSSGenerator < Finders::WpVersion::RSSGenerator
|
class RSSGenerator < WPScan::Finders::WpVersion::RSSGenerator
|
||||||
def process_urls(urls, _opts = {})
|
def process_urls(urls, _opts = {})
|
||||||
found = []
|
found = []
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ module WPScan
|
|||||||
# @param [ WPScan::Target ] target
|
# @param [ WPScan::Target ] target
|
||||||
def initialize(target)
|
def initialize(target)
|
||||||
(%w[RSSGenerator AtomGenerator RDFGenerator] +
|
(%w[RSSGenerator AtomGenerator RDFGenerator] +
|
||||||
DB::DynamicFinders::Wordpress.versions_finders_configs.keys +
|
WPScan::DB::DynamicFinders::Wordpress.versions_finders_configs.keys +
|
||||||
%w[Readme UniqueFingerprinting]
|
%w[Readme UniqueFingerprinting]
|
||||||
).each do |finder_name|
|
).each do |finder_name|
|
||||||
finders << WpVersion.const_get(finder_name.to_sym).new(target)
|
finders << WpVersion.const_get(finder_name.to_sym).new(target)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ module WPScan
|
|||||||
#
|
#
|
||||||
# Empty classes for the #type to be correctly displayed (as taken from the self.class from the parent)
|
# Empty classes for the #type to be correctly displayed (as taken from the self.class from the parent)
|
||||||
#
|
#
|
||||||
class BackupDB < InterestingFinding
|
class PluginBackupFolder < InterestingFinding
|
||||||
end
|
end
|
||||||
|
|
||||||
class DebugLog < InterestingFinding
|
class DebugLog < InterestingFinding
|
||||||
|
|||||||
@@ -15,16 +15,9 @@ module WPScan
|
|||||||
@uri = Addressable::URI.parse(blog.url(path_from_blog))
|
@uri = Addressable::URI.parse(blog.url(path_from_blog))
|
||||||
end
|
end
|
||||||
|
|
||||||
# Retrieve the metadata from the vuln API if available (and a valid token is given),
|
# @return [ JSON ]
|
||||||
# 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::VulnApi.plugin_data(slug)
|
@db_data ||= DB::Plugin.db_data(slug)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
@@ -35,11 +28,6 @@ module WPScan
|
|||||||
|
|
||||||
@version
|
@version
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Array<String> ]
|
|
||||||
def potential_readme_filenames
|
|
||||||
@potential_readme_filenames ||= [*(DB::DynamicFinders::Plugin.df_data.dig(slug, 'Readme', 'path') || super)]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -21,16 +21,9 @@ 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::VulnApi.theme_data(slug)
|
@db_data ||= DB::Theme.db_data(slug)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ module WPScan
|
|||||||
include CMSScanner::Target::Platform::PHP
|
include CMSScanner::Target::Platform::PHP
|
||||||
include CMSScanner::Target::Server::Generic
|
include CMSScanner::Target::Server::Generic
|
||||||
|
|
||||||
# Most common readme filenames, based on checking all public plugins and themes.
|
|
||||||
READMES = %w[readme.txt README.txt README.md readme.md Readme.txt].freeze
|
READMES = %w[readme.txt README.txt README.md readme.md Readme.txt].freeze
|
||||||
|
|
||||||
attr_reader :uri, :slug, :detection_opts, :version_detection_opts, :blog, :path_from_blog, :db_data
|
attr_reader :uri, :slug, :detection_opts, :version_detection_opts, :blog, :path_from_blog, :db_data
|
||||||
@@ -60,18 +59,18 @@ module WPScan
|
|||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def latest_version
|
def latest_version
|
||||||
@latest_version ||= metadata['latest_version'] ? Model::Version.new(metadata['latest_version']) : nil
|
@latest_version ||= db_data['latest_version'] ? Model::Version.new(db_data['latest_version']) : nil
|
||||||
end
|
end
|
||||||
|
|
||||||
# Not used anywhere ATM
|
# Not used anywhere ATM
|
||||||
# @return [ Boolean ]
|
# @return [ Boolean ]
|
||||||
def popular?
|
def popular?
|
||||||
@popular ||= metadata['popular'] ? true : false
|
@popular ||= db_data['popular']
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def last_updated
|
def last_updated
|
||||||
@last_updated ||= metadata['last_updated']
|
@last_updated ||= db_data['last_updated']
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Boolean ]
|
# @return [ Boolean ]
|
||||||
@@ -118,7 +117,7 @@ module WPScan
|
|||||||
|
|
||||||
return @readme_url unless @readme_url.nil?
|
return @readme_url unless @readme_url.nil?
|
||||||
|
|
||||||
potential_readme_filenames.each do |path|
|
READMES.each do |path|
|
||||||
t_url = url(path)
|
t_url = url(path)
|
||||||
|
|
||||||
return @readme_url = t_url if Browser.forge_request(t_url, blog.head_or_get_params).run.code == 200
|
return @readme_url = t_url if Browser.forge_request(t_url, blog.head_or_get_params).run.code == 200
|
||||||
@@ -127,10 +126,6 @@ module WPScan
|
|||||||
@readme_url = false
|
@readme_url = false
|
||||||
end
|
end
|
||||||
|
|
||||||
def potential_readme_filenames
|
|
||||||
@potential_readme_filenames ||= READMES
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param [ String ] path
|
# @param [ String ] path
|
||||||
# @param [ Hash ] params The request params
|
# @param [ Hash ] params The request params
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -35,16 +35,9 @@ 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
|
||||||
|
|
||||||
# Retrieve the metadata from the vuln API if available (and a valid token is given),
|
# @return [ JSON ]
|
||||||
# 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::VulnApi.wordpress_data(number)
|
@db_data ||= DB::Version.db_data(number)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Array<Vulnerability> ]
|
# @return [ Array<Vulnerability> ]
|
||||||
@@ -62,12 +55,12 @@ module WPScan
|
|||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def release_date
|
def release_date
|
||||||
@release_date ||= metadata['release_date'] || 'Unknown'
|
@release_date ||= db_data['release_date'] || 'Unknown'
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def status
|
def status
|
||||||
@status ||= metadata['status'] || 'Unknown'
|
@status ||= db_data['status'] || 'Unknown'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ _______________________________________________________________
|
|||||||
|
|
||||||
WordPress Security Scanner by the WPScan Team
|
WordPress Security Scanner by the WPScan Team
|
||||||
Version <%= WPScan::VERSION %>
|
Version <%= WPScan::VERSION %>
|
||||||
<%= ' ' * ((63 - WPScan::DB::Sponsor.text.length)/2) + WPScan::DB::Sponsor.text %>
|
Sponsored by Sucuri - https://sucuri.net
|
||||||
@_WPScan_, @ethicalhack3r, @erwan_lr, @_FireFart_
|
@_WPScan_, @ethicalhack3r, @erwan_lr, @_FireFart_
|
||||||
_______________________________________________________________
|
_______________________________________________________________
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<%= notice_icon %> Config Backup(s) Identified:
|
<%= notice_icon %> Config Backup(s) Identified:
|
||||||
<% @config_backups.each do |config_backup| -%>
|
<% @config_backups.each do |config_backup| -%>
|
||||||
|
|
||||||
<%= critical_icon %> <%= config_backup %>
|
<%= info_icon %> <%= config_backup %>
|
||||||
<%= render('@finding', item: config_backup) -%>
|
<%= render('@finding', item: config_backup) -%>
|
||||||
<% end -%>
|
<% end -%>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<%= notice_icon %> Db Export(s) Identified:
|
<%= notice_icon %> Db Export(s) Identified:
|
||||||
<% @db_exports.each do |db_export| -%>
|
<% @db_exports.each do |db_export| -%>
|
||||||
|
|
||||||
<%= critical_icon %> <%= db_export %>
|
<%= info_icon %> <%= db_export %>
|
||||||
<%= render('@finding', item: db_export) -%>
|
<%= render('@finding', item: db_export) -%>
|
||||||
<% end -%>
|
<% end -%>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
<% 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 -%>
|
|
||||||
@@ -7,5 +7,5 @@
|
|||||||
"@erwan_lr",
|
"@erwan_lr",
|
||||||
"@_FireFart_"
|
"@_FireFart_"
|
||||||
],
|
],
|
||||||
"sponsor": <%= WPScan::DB::Sponsor.text.to_json %>
|
"sponsored_by": "Sucuri - https://sucuri.net"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,10 +11,9 @@
|
|||||||
}<% unless index == last_index %>,<% end -%>
|
}<% unless index == last_index %>,<% end -%>
|
||||||
<% end -%>
|
<% end -%>
|
||||||
<% end -%>
|
<% end -%>
|
||||||
}
|
},
|
||||||
<% if @item.respond_to?(:vulnerabilities) -%>
|
"vulnerabilities": [
|
||||||
,"vulnerabilities": [
|
<% if @item.respond_to?(:vulnerabilities) && !(vulns = @item.vulnerabilities).empty? -%>
|
||||||
<% unless (vulns = @item.vulnerabilities).empty? -%>
|
|
||||||
<% last_index = vulns.size - 1 -%>
|
<% last_index = vulns.size - 1 -%>
|
||||||
<% vulns.each_with_index do |v, index| -%>
|
<% vulns.each_with_index do |v, index| -%>
|
||||||
{
|
{
|
||||||
@@ -24,5 +23,4 @@
|
|||||||
}<% unless index == last_index -%>,<% end -%>
|
}<% unless index == last_index -%>,<% end -%>
|
||||||
<% end -%>
|
<% end -%>
|
||||||
<% end -%>
|
<% end -%>
|
||||||
]
|
]
|
||||||
<% end -%>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
"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,6 @@ 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,7 +7,6 @@ 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,7 +12,6 @@ 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,8 +13,7 @@ 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'
|
||||||
@@ -39,28 +38,12 @@ 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
|
||||||
@default_user_agent ||= "WPScan v#{VERSION} (https://wpscan.org/)"
|
"WPScan v#{VERSION} (https://wpscan.org/)"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,12 +7,9 @@ require_relative 'db/plugins'
|
|||||||
require_relative 'db/themes'
|
require_relative 'db/themes'
|
||||||
require_relative 'db/plugin'
|
require_relative 'db/plugin'
|
||||||
require_relative 'db/theme'
|
require_relative 'db/theme'
|
||||||
require_relative 'db/sponsor'
|
|
||||||
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'
|
||||||
|
|||||||
@@ -5,19 +5,18 @@ module WPScan
|
|||||||
module DynamicFinders
|
module DynamicFinders
|
||||||
class Base
|
class Base
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def self.df_file
|
def self.db_file
|
||||||
@df_file ||= DB_DIR.join('dynamic_finders.yml').to_s
|
@db_file ||= DB_DIR.join('dynamic_finders.yml').to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def self.all_df_data
|
def self.db_data
|
||||||
@all_df_data ||= YAML.safe_load(File.read(df_file), [Regexp])
|
# true allows aliases to be loaded
|
||||||
|
@db_data ||= YAML.safe_load(File.read(db_file), [Regexp], [], true)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Array<Symbol> ]
|
# @return [ Array<Symbol> ]
|
||||||
def self.allowed_classes
|
def self.allowed_classes
|
||||||
# The Readme is not put in there as it's not a Real DF, but rather using the DF system
|
|
||||||
# to get the list of potential filenames for a given slug
|
|
||||||
@allowed_classes ||= %i[Comment Xpath HeaderPattern BodyPattern JavascriptVar QueryParameter ConfigParser]
|
@allowed_classes ||= %i[Comment Xpath HeaderPattern BodyPattern JavascriptVar QueryParameter ConfigParser]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ module WPScan
|
|||||||
module DynamicFinders
|
module DynamicFinders
|
||||||
class Plugin < Base
|
class Plugin < Base
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def self.df_data
|
def self.db_data
|
||||||
@df_data ||= all_df_data['plugins'] || {}
|
@db_data ||= super['plugins'] || {}
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.version_finder_module
|
def self.version_finder_module
|
||||||
@@ -21,7 +21,7 @@ module WPScan
|
|||||||
|
|
||||||
return configs unless allowed_classes.include?(finder_class)
|
return configs unless allowed_classes.include?(finder_class)
|
||||||
|
|
||||||
df_data.each do |slug, finders|
|
db_data.each do |slug, finders|
|
||||||
# Quite sure better can be done with some kind of logic statement in the select
|
# Quite sure better can be done with some kind of logic statement in the select
|
||||||
fs = if aggressive
|
fs = if aggressive
|
||||||
finders.reject { |_f, c| c['path'].nil? }
|
finders.reject { |_f, c| c['path'].nil? }
|
||||||
@@ -48,7 +48,7 @@ module WPScan
|
|||||||
|
|
||||||
@versions_finders_configs = {}
|
@versions_finders_configs = {}
|
||||||
|
|
||||||
df_data.each do |slug, finders|
|
db_data.each do |slug, finders|
|
||||||
finders.each do |finder_name, config|
|
finders.each do |finder_name, config|
|
||||||
next unless config.key?('version')
|
next unless config.key?('version')
|
||||||
|
|
||||||
@@ -73,33 +73,23 @@ module WPScan
|
|||||||
version_finder_module.const_get(constant_name)
|
version_finder_module.const_get(constant_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Create the dynamic finders related to the given slug, and return the created classes
|
def self.create_versions_finders
|
||||||
#
|
versions_finders_configs.each do |slug, finders|
|
||||||
# @param [ String ] slug
|
mod = maybe_create_module(slug)
|
||||||
#
|
|
||||||
# @return [ Array<Class> ] The created classes
|
|
||||||
def self.create_versions_finders(slug)
|
|
||||||
created = []
|
|
||||||
mod = maybe_create_module(slug)
|
|
||||||
|
|
||||||
versions_finders_configs[slug]&.each do |finder_class, config|
|
finders.each do |finder_class, config|
|
||||||
klass = config['class'] || finder_class
|
klass = config['class'] || finder_class
|
||||||
|
|
||||||
# Instead of raising exceptions, skip unallowed/already defined finders
|
# Instead of raising exceptions, skip unallowed/already defined finders
|
||||||
# So that, when new DF configs are put in the .yml
|
# So that, when new DF configs are put in the .yml
|
||||||
# users with old version of WPScan will still be able to scan blogs
|
# users with old version of WPScan will still be able to scan blogs
|
||||||
# when updating the DB but not the tool
|
# when updating the DB but not the tool
|
||||||
|
next if mod.constants.include?(finder_class.to_sym) ||
|
||||||
|
!allowed_classes.include?(klass.to_sym)
|
||||||
|
|
||||||
next unless allowed_classes.include?(klass.to_sym)
|
version_finder_super_class(klass).create_child_class(mod, finder_class.to_sym, config)
|
||||||
|
end
|
||||||
created << if mod.constants.include?(finder_class.to_sym)
|
|
||||||
mod.const_get(finder_class.to_sym)
|
|
||||||
else
|
|
||||||
version_finder_super_class(klass).create_child_class(mod, finder_class.to_sym, config)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
created
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# The idea here would be to check if the class exist in
|
# The idea here would be to check if the class exist in
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ module WPScan
|
|||||||
module DynamicFinders
|
module DynamicFinders
|
||||||
class Theme < Plugin
|
class Theme < Plugin
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def self.df_data
|
def self.db_data
|
||||||
@df_data ||= all_df_data['themes'] || {}
|
@db_data ||= super['themes'] || {}
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.version_finder_module
|
def self.version_finder_module
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ module WPScan
|
|||||||
module DynamicFinders
|
module DynamicFinders
|
||||||
class Wordpress < Base
|
class Wordpress < Base
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def self.df_data
|
def self.db_data
|
||||||
@df_data ||= all_df_data['wordpress'] || {}
|
@db_data ||= super['wordpress'] || {}
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Constant ]
|
# @return [ Constant ]
|
||||||
@@ -30,9 +30,9 @@ module WPScan
|
|||||||
return configs unless allowed_classes.include?(finder_class)
|
return configs unless allowed_classes.include?(finder_class)
|
||||||
|
|
||||||
finders = if aggressive
|
finders = if aggressive
|
||||||
df_data.reject { |_f, c| c['path'].nil? }
|
db_data.reject { |_f, c| c['path'].nil? }
|
||||||
else
|
else
|
||||||
df_data.select { |_f, c| c['path'].nil? }
|
db_data.select { |_f, c| c['path'].nil? }
|
||||||
end
|
end
|
||||||
|
|
||||||
finders.each do |finder_name, config|
|
finders.each do |finder_name, config|
|
||||||
@@ -48,7 +48,7 @@ module WPScan
|
|||||||
|
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def self.versions_finders_configs
|
def self.versions_finders_configs
|
||||||
@versions_finders_configs ||= df_data.select { |_finder_name, config| config.key?('version') }
|
@versions_finders_configs ||= db_data.select { |_finder_name, config| config.key?('version') }
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.create_versions_finders
|
def self.create_versions_finders
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ module WPScan
|
|||||||
module DB
|
module DB
|
||||||
# Plugin DB
|
# Plugin DB
|
||||||
class Plugin < WpItem
|
class Plugin < WpItem
|
||||||
# @return [ Hash ]
|
# @return [ String ]
|
||||||
def self.metadata
|
def self.db_file
|
||||||
@metadata ||= super['plugins'] || {}
|
@db_file ||= DB_DIR.join('plugins.json').to_s
|
||||||
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.metadata
|
def self.db
|
||||||
Plugin.metadata
|
Plugin.db
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module WPScan
|
|
||||||
module DB
|
|
||||||
class Sponsor
|
|
||||||
# @return [ Hash ]
|
|
||||||
def self.text
|
|
||||||
@text ||= file_path.exist? ? File.read(file_path).chomp : ''
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.file_path
|
|
||||||
@file_path ||= DB_DIR.join('sponsor.txt')
|
|
||||||
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 [ Hash ]
|
# @return [ String ]
|
||||||
def self.metadata
|
def self.db_file
|
||||||
@metadata ||= super['themes'] || {}
|
@db_file ||= DB_DIR.join('themes.json').to_s
|
||||||
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.metadata
|
def self.db
|
||||||
Theme.metadata
|
Theme.db
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,15 +7,12 @@ 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[
|
||||||
metadata.json wp_fingerprints.json
|
plugins.json themes.json wordpresses.json
|
||||||
timthumbs-v3.txt config_backups.txt db_exports.txt
|
timthumbs-v3.txt config_backups.txt db_exports.txt
|
||||||
dynamic_finders.yml LICENSE sponsor.txt
|
dynamic_finders.yml wp_fingerprints.json LICENSE
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
OLD_FILES = %w[
|
OLD_FILES = %w[wordpress.db user-agents.txt dynamic_finders_01.yml].freeze
|
||||||
wordpress.db user-agents.txt dynamic_finders_01.yml
|
|
||||||
wordpresses.json plugins.json themes.json
|
|
||||||
].freeze
|
|
||||||
|
|
||||||
attr_reader :repo_directory
|
attr_reader :repo_directory
|
||||||
|
|
||||||
@@ -67,12 +64,11 @@ module WPScan
|
|||||||
# @return [ Hash ] The params for Typhoeus::Request
|
# @return [ Hash ] The params for Typhoeus::Request
|
||||||
# @note Those params can't be overriden by CLI options
|
# @note Those params can't be overriden by CLI options
|
||||||
def request_params
|
def request_params
|
||||||
@request_params ||= {
|
{
|
||||||
timeout: 600,
|
timeout: 600,
|
||||||
connecttimeout: 300,
|
connecttimeout: 300,
|
||||||
accept_encoding: 'gzip, deflate',
|
accept_encoding: 'gzip, deflate',
|
||||||
cache_ttl: 0,
|
cache_ttl: 0
|
||||||
headers: { 'User-Agent' => Browser.instance.default_user_agent, 'Referer' => nil }
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
# 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,19 +6,14 @@ 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 metadata associated to the identifier
|
# @return [ Hash ] The JSON data from the DB associated to the identifier
|
||||||
def self.metadata_at(identifier)
|
def self.db_data(identifier)
|
||||||
metadata[identifier] || {}
|
db[identifier] || {}
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ JSON ]
|
# @return [ JSON ]
|
||||||
def self.metadata
|
def self.db
|
||||||
@metadata ||= read_json_file(metadata_file)
|
@db ||= read_json_file(db_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
|
||||||
metadata.keys
|
db.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
|
||||||
metadata.select { |_key, item| item['popular'] == true }.keys
|
db.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
|
||||||
metadata.select { |_key, item| item['vulnerabilities'] == true }.keys
|
db.reject { |_key, item| item['vulnerabilities'].empty? }.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 [ Hash ]
|
# @return [ String ]
|
||||||
def self.metadata
|
def self.db_file
|
||||||
@metadata ||= super['wordpress'] || {}
|
@db_file ||= DB_DIR.join('wordpresses.json').to_s
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ module WPScan
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
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'
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module WPScan
|
|
||||||
module Error
|
|
||||||
class PluginsThresholdReached < Standard
|
|
||||||
def to_s
|
|
||||||
"The number of plugins detected reached the threshold of #{ParsedCli.plugins_threshold} " \
|
|
||||||
'which might indicate False Positive. It would be recommended to use the --exclude-content-based ' \
|
|
||||||
'option to ignore the bad responses.'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class ThemesThresholdReached < Standard
|
|
||||||
def to_s
|
|
||||||
"The number of themes detected reached the threshold of #{ParsedCli.themes_threshold} " \
|
|
||||||
'which might indicate False Positive. It would be recommended to use the --exclude-content-based ' \
|
|
||||||
'option to ignore the bad responses.'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
# 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
|
|
||||||
@@ -4,9 +4,9 @@ module WPScan
|
|||||||
module Finders
|
module Finders
|
||||||
module DynamicFinder
|
module DynamicFinder
|
||||||
module Version
|
module Version
|
||||||
# Version finder using Body Pattern method. Typically used when the response is not
|
# Version finder using Body Pattern method. Tipically used when the response is not
|
||||||
# an HTML doc and Xpath can't be used
|
# an HTML doc and Xpath can't be used
|
||||||
class BodyPattern < Finders::DynamicFinder::Version::Finder
|
class BodyPattern < WPScan::Finders::DynamicFinder::Version::Finder
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def self.child_class_constants
|
def self.child_class_constants
|
||||||
@child_class_constants ||= super().merge(PATTERN: nil, CONFIDENCE: 60)
|
@child_class_constants ||= super().merge(PATTERN: nil, CONFIDENCE: 60)
|
||||||
@@ -16,7 +16,7 @@ module WPScan
|
|||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
# @return [ Version ]
|
# @return [ Version ]
|
||||||
def find(response, _opts = {})
|
def find(response, _opts = {})
|
||||||
return unless response.code != 404 && response.body =~ self.class::PATTERN
|
return unless response.body =~ self.class::PATTERN
|
||||||
|
|
||||||
create_version(
|
create_version(
|
||||||
Regexp.last_match[:v],
|
Regexp.last_match[:v],
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ module WPScan
|
|||||||
module Version
|
module Version
|
||||||
# Version finder in Comment, which is basically an Xpath one with a default
|
# Version finder in Comment, which is basically an Xpath one with a default
|
||||||
# Xpath of //comment()
|
# Xpath of //comment()
|
||||||
class Comment < Finders::DynamicFinder::Version::Xpath
|
class Comment < WPScan::Finders::DynamicFinder::Version::Xpath
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def self.child_class_constants
|
def self.child_class_constants
|
||||||
@child_class_constants ||= super().merge(PATTERN: nil, XPATH: '//comment()')
|
@child_class_constants ||= super().merge(PATTERN: nil, XPATH: '//comment()')
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ module WPScan
|
|||||||
module Version
|
module Version
|
||||||
# Version finder using by parsing config files, such as composer.json
|
# Version finder using by parsing config files, such as composer.json
|
||||||
# and so on
|
# and so on
|
||||||
class ConfigParser < Finders::DynamicFinder::Version::Finder
|
class ConfigParser < WPScan::Finders::DynamicFinder::Version::Finder
|
||||||
ALLOWED_PARSERS = [JSON, YAML].freeze
|
ALLOWED_PARSERS = [JSON, YAML].freeze
|
||||||
|
|
||||||
def self.child_class_constants
|
def self.child_class_constants
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ module WPScan
|
|||||||
module DynamicFinder
|
module DynamicFinder
|
||||||
module Version
|
module Version
|
||||||
# Version finder using Header Pattern method
|
# Version finder using Header Pattern method
|
||||||
class HeaderPattern < Finders::DynamicFinder::Version::Finder
|
class HeaderPattern < WPScan::Finders::DynamicFinder::Version::Finder
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def self.child_class_constants
|
def self.child_class_constants
|
||||||
@child_class_constants ||= super().merge(HEADER: nil, PATTERN: nil, CONFIDENCE: 60)
|
@child_class_constants ||= super().merge(HEADER: nil, PATTERN: nil, CONFIDENCE: 60)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ module WPScan
|
|||||||
module DynamicFinder
|
module DynamicFinder
|
||||||
module Version
|
module Version
|
||||||
# Version finder using JavaScript Variable method
|
# Version finder using JavaScript Variable method
|
||||||
class JavascriptVar < Finders::DynamicFinder::Version::Finder
|
class JavascriptVar < WPScan::Finders::DynamicFinder::Version::Finder
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def self.child_class_constants
|
def self.child_class_constants
|
||||||
@child_class_constants ||= super().merge(
|
@child_class_constants ||= super().merge(
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ module WPScan
|
|||||||
module DynamicFinder
|
module DynamicFinder
|
||||||
module Version
|
module Version
|
||||||
# Version finder using QueryParameter method
|
# Version finder using QueryParameter method
|
||||||
class QueryParameter < Finders::DynamicFinder::Version::Finder
|
class QueryParameter < WPScan::Finders::DynamicFinder::Version::Finder
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def self.child_class_constants
|
def self.child_class_constants
|
||||||
@child_class_constants ||= super().merge(
|
@child_class_constants ||= super().merge(
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ module WPScan
|
|||||||
module DynamicFinder
|
module DynamicFinder
|
||||||
module Version
|
module Version
|
||||||
# Version finder using Xpath method
|
# Version finder using Xpath method
|
||||||
class Xpath < Finders::DynamicFinder::Version::Finder
|
class Xpath < WPScan::Finders::DynamicFinder::Version::Finder
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def self.child_class_constants
|
def self.child_class_constants
|
||||||
@child_class_constants ||= super().merge(
|
@child_class_constants ||= super().merge(
|
||||||
|
|||||||
@@ -4,22 +4,22 @@ module WPScan
|
|||||||
module Finders
|
module Finders
|
||||||
module DynamicFinder
|
module DynamicFinder
|
||||||
module WpItemVersion
|
module WpItemVersion
|
||||||
class BodyPattern < Finders::DynamicFinder::Version::BodyPattern
|
class BodyPattern < WPScan::Finders::DynamicFinder::Version::BodyPattern
|
||||||
end
|
end
|
||||||
|
|
||||||
class Comment < Finders::DynamicFinder::Version::Comment
|
class Comment < WPScan::Finders::DynamicFinder::Version::Comment
|
||||||
end
|
end
|
||||||
|
|
||||||
class ConfigParser < Finders::DynamicFinder::Version::ConfigParser
|
class ConfigParser < WPScan::Finders::DynamicFinder::Version::ConfigParser
|
||||||
end
|
end
|
||||||
|
|
||||||
class HeaderPattern < Finders::DynamicFinder::Version::HeaderPattern
|
class HeaderPattern < WPScan::Finders::DynamicFinder::Version::HeaderPattern
|
||||||
end
|
end
|
||||||
|
|
||||||
class JavascriptVar < Finders::DynamicFinder::Version::JavascriptVar
|
class JavascriptVar < WPScan::Finders::DynamicFinder::Version::JavascriptVar
|
||||||
end
|
end
|
||||||
|
|
||||||
class QueryParameter < Finders::DynamicFinder::Version::QueryParameter
|
class QueryParameter < WPScan::Finders::DynamicFinder::Version::QueryParameter
|
||||||
# @return [ Regexp ]
|
# @return [ Regexp ]
|
||||||
def path_pattern
|
def path_pattern
|
||||||
# TODO: consider the target.blog.themes_dir if the target is a Theme (maybe implement a WpItem#item_dir ?)
|
# TODO: consider the target.blog.themes_dir if the target is a Theme (maybe implement a WpItem#item_dir ?)
|
||||||
@@ -37,7 +37,7 @@ module WPScan
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class Xpath < Finders::DynamicFinder::Version::Xpath
|
class Xpath < WPScan::Finders::DynamicFinder::Version::Xpath
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -12,23 +12,23 @@ module WPScan
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class BodyPattern < Finders::DynamicFinder::Version::BodyPattern
|
class BodyPattern < WPScan::Finders::DynamicFinder::Version::BodyPattern
|
||||||
include Finder
|
include Finder
|
||||||
end
|
end
|
||||||
|
|
||||||
class Comment < Finders::DynamicFinder::Version::Comment
|
class Comment < WPScan::Finders::DynamicFinder::Version::Comment
|
||||||
include Finder
|
include Finder
|
||||||
end
|
end
|
||||||
|
|
||||||
class HeaderPattern < Finders::DynamicFinder::Version::HeaderPattern
|
class HeaderPattern < WPScan::Finders::DynamicFinder::Version::HeaderPattern
|
||||||
include Finder
|
include Finder
|
||||||
end
|
end
|
||||||
|
|
||||||
class JavascriptVar < Finders::DynamicFinder::Version::JavascriptVar
|
class JavascriptVar < WPScan::Finders::DynamicFinder::Version::JavascriptVar
|
||||||
include Finder
|
include Finder
|
||||||
end
|
end
|
||||||
|
|
||||||
class QueryParameter < Finders::DynamicFinder::Version::QueryParameter
|
class QueryParameter < WPScan::Finders::DynamicFinder::Version::QueryParameter
|
||||||
include Finder
|
include Finder
|
||||||
|
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
|
|||||||
@@ -6,15 +6,13 @@ rescue StandardError => e
|
|||||||
raise "JSON parsing error in #{file} #{e}"
|
raise "JSON parsing error in #{file} #{e}"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Sanitize and classify a slug
|
|
||||||
# @note As a class can not start with a digit or underscore, a D_ is
|
|
||||||
# put as a prefix in such case. Ugly but well :x
|
|
||||||
# Not only used to classify slugs though, but Dynamic Finder names as well
|
|
||||||
#
|
|
||||||
# @return [ Symbol ]
|
# @return [ Symbol ]
|
||||||
|
# @note As a class can not start with a digit or underscore, a D_ is
|
||||||
|
# put as a prefix in such case. Ugly but well :x
|
||||||
|
# Not only used to classify slugs though, but Dynamic Finder names as well
|
||||||
def classify_slug(slug)
|
def classify_slug(slug)
|
||||||
classified = slug.to_s.gsub(/[^a-z\d\-]/i, '-').gsub(/\-{1,}/, '_').camelize.to_s
|
classified = slug.to_s.tr('-', '_').camelize.to_s
|
||||||
classified = "D_#{classified}" if /\d/.match?(classified[0])
|
classified = "D_#{classified}" if classified[0] =~ /\d/
|
||||||
|
|
||||||
classified.to_sym
|
classified.to_sym
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ module WPScan
|
|||||||
end
|
end
|
||||||
|
|
||||||
homepage_res.html.css('meta[name="generator"]').each do |node|
|
homepage_res.html.css('meta[name="generator"]').each do |node|
|
||||||
return true if /wordpress/i.match?(node['content'])
|
return true if node['content'] =~ /wordpress/i
|
||||||
end
|
end
|
||||||
|
|
||||||
return true unless comments_from_page(/wordpress/i, homepage_res).empty?
|
return true unless comments_from_page(/wordpress/i, homepage_res).empty?
|
||||||
|
|||||||
@@ -99,19 +99,20 @@ module WPScan
|
|||||||
|
|
||||||
# @return [ String, False ] String of the sub_dir found, false otherwise
|
# @return [ String, False ] String of the sub_dir found, false otherwise
|
||||||
# @note: nil can not be returned here, otherwise if there is no sub_dir
|
# @note: nil can not be returned here, otherwise if there is no sub_dir
|
||||||
# the check would be done each time, which would make enumeration of
|
# the check would be done each time
|
||||||
# long list of items very slow to generate
|
|
||||||
def sub_dir
|
def sub_dir
|
||||||
return @sub_dir unless @sub_dir.nil?
|
unless @sub_dir
|
||||||
|
# url_pattern is from CMSScanner::Target
|
||||||
|
pattern = %r{#{url_pattern}(.+?)/(?:xmlrpc\.php|wp\-includes/)}i
|
||||||
|
|
||||||
# url_pattern is from CMSScanner::Target
|
in_scope_uris(homepage_res) do |uri|
|
||||||
pattern = %r{#{url_pattern}(.+?)/(?:xmlrpc\.php|wp\-includes/)}i
|
return @sub_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
|
||||||
|
end
|
||||||
|
|
||||||
in_scope_uris(homepage_res) do |uri|
|
@sub_dir = false
|
||||||
return @sub_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@sub_dir = false
|
@sub_dir
|
||||||
end
|
end
|
||||||
|
|
||||||
# Override of the WebSite#url to consider the custom WP directories
|
# Override of the WebSite#url to consider the custom WP directories
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
# 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.7.0'
|
VERSION = '3.5.3'
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -70,8 +70,8 @@ describe WPScan::Controller::Enumeration do
|
|||||||
it 'contains the correct options' do
|
it 'contains the correct options' do
|
||||||
expect(controller.cli_options.map(&:to_sym)).to eql(
|
expect(controller.cli_options.map(&:to_sym)).to eql(
|
||||||
%i[enumerate exclude_content_based
|
%i[enumerate exclude_content_based
|
||||||
plugins_list plugins_detection plugins_version_all plugins_version_detection plugins_threshold
|
plugins_list plugins_detection plugins_version_all plugins_version_detection
|
||||||
themes_list themes_detection themes_version_all themes_version_detection themes_threshold
|
themes_list themes_detection themes_version_all themes_version_detection
|
||||||
timthumbs_list timthumbs_detection
|
timthumbs_list timthumbs_detection
|
||||||
config_backups_list config_backups_detection
|
config_backups_list config_backups_detection
|
||||||
db_exports_list db_exports_detection
|
db_exports_list db_exports_detection
|
||||||
@@ -102,6 +102,15 @@ describe WPScan::Controller::Enumeration do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#before_scan' do
|
||||||
|
it 'creates the Dynamic Finders' do
|
||||||
|
expect(WPScan::DB::DynamicFinders::Plugin).to receive(:create_versions_finders)
|
||||||
|
expect(WPScan::DB::DynamicFinders::Theme).to receive(:create_versions_finders)
|
||||||
|
|
||||||
|
controller.before_scan
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#run' do
|
describe '#run' do
|
||||||
context 'when no :enumerate' do
|
context 'when no :enumerate' do
|
||||||
before do
|
before do
|
||||||
|
|||||||
@@ -52,60 +52,6 @@ describe WPScan::Controller::PasswordAttack do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#xmlrpc_get_users_blogs_enabled?' do
|
|
||||||
before { expect(controller.target).to receive(:xmlrpc).and_return(xmlrpc) }
|
|
||||||
|
|
||||||
context 'when xmlrpc not found' do
|
|
||||||
let(:xmlrpc) { nil }
|
|
||||||
|
|
||||||
its(:xmlrpc_get_users_blogs_enabled?) { should be false }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when xmlrpc not enabled' do
|
|
||||||
let(:xmlrpc) { WPScan::Model::XMLRPC.new("#{target_url}xmlrpc.php") }
|
|
||||||
|
|
||||||
it 'returns false' do
|
|
||||||
expect(xmlrpc).to receive(:enabled?).and_return(false)
|
|
||||||
|
|
||||||
expect(controller.xmlrpc_get_users_blogs_enabled?).to be false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when xmlrpc enabled' do
|
|
||||||
let(:xmlrpc) { WPScan::Model::XMLRPC.new("#{target_url}xmlrpc.php") }
|
|
||||||
|
|
||||||
before { expect(xmlrpc).to receive(:enabled?).and_return(true) }
|
|
||||||
|
|
||||||
context 'when wp.getUsersBlogs methods not listed' do
|
|
||||||
it 'returns false' do
|
|
||||||
expect(xmlrpc).to receive(:available_methods).and_return(%w[m1 m2])
|
|
||||||
|
|
||||||
expect(controller.xmlrpc_get_users_blogs_enabled?).to be false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when wp.getUsersBlogs method listed' do
|
|
||||||
before { expect(xmlrpc).to receive(:available_methods).and_return(%w[wp.getUsersBlogs m2]) }
|
|
||||||
|
|
||||||
context 'when wp.getUsersBlogs method disabled' do
|
|
||||||
it 'returns false' do
|
|
||||||
stub_request(:post, xmlrpc.url).to_return(body: 'XML-RPC services are disabled on this site.')
|
|
||||||
|
|
||||||
expect(controller.xmlrpc_get_users_blogs_enabled?).to be false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when wp.getUsersBlogs method enabled' do
|
|
||||||
it 'returns true' do
|
|
||||||
stub_request(:post, xmlrpc.url).to_return(body: 'Incorrect username or password.')
|
|
||||||
|
|
||||||
expect(controller.xmlrpc_get_users_blogs_enabled?).to be true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#attacker' do
|
describe '#attacker' do
|
||||||
context 'when --password-attack provided' do
|
context 'when --password-attack provided' do
|
||||||
let(:cli_args) { "#{super()} --password-attack #{attack}" }
|
let(:cli_args) { "#{super()} --password-attack #{attack}" }
|
||||||
@@ -146,7 +92,7 @@ describe WPScan::Controller::PasswordAttack do
|
|||||||
before do
|
before do
|
||||||
expect(controller.target)
|
expect(controller.target)
|
||||||
.to receive(:xmlrpc)
|
.to receive(:xmlrpc)
|
||||||
.and_return(WPScan::Model::XMLRPC.new("#{target_url}xmlrpc.php"))
|
.and_return(WPScan::Model::XMLRPC.new("#{target_url}/xmlrpc.php"))
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when single xmlrpc' do
|
context 'when single xmlrpc' do
|
||||||
@@ -171,50 +117,73 @@ describe WPScan::Controller::PasswordAttack do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context 'when automatic detection' do
|
context 'when automatic detection' do
|
||||||
context 'when xmlrpc_get_users_blogs_enabled? is false' do
|
before { expect(controller.target).to receive(:xmlrpc).and_return(xmlrpc) }
|
||||||
|
|
||||||
|
context 'when xmlrpc not found' do
|
||||||
|
let(:xmlrpc) { nil }
|
||||||
|
|
||||||
it 'returns the WpLogin' do
|
it 'returns the WpLogin' do
|
||||||
expect(controller).to receive(:xmlrpc_get_users_blogs_enabled?).and_return(false)
|
expect(controller.attacker).to be_a WPScan::Finders::Passwords::WpLogin
|
||||||
|
expect(controller.attacker.target).to be_a WPScan::Target
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when xmlrpc not enabled' do
|
||||||
|
let(:xmlrpc) { WPScan::Model::XMLRPC.new("#{target_url}/xmlrpc.php") }
|
||||||
|
|
||||||
|
it 'returns the WpLogin' do
|
||||||
|
expect(xmlrpc).to receive(:enabled?).and_return(false)
|
||||||
|
|
||||||
expect(controller.attacker).to be_a WPScan::Finders::Passwords::WpLogin
|
expect(controller.attacker).to be_a WPScan::Finders::Passwords::WpLogin
|
||||||
expect(controller.attacker.target).to be_a WPScan::Target
|
expect(controller.attacker.target).to be_a WPScan::Target
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when xmlrpc_get_users_blogs_enabled? is true' do
|
context 'when xmlrpc enabled' do
|
||||||
before do
|
let(:xmlrpc) { WPScan::Model::XMLRPC.new("#{target_url}/xmlrpc.php") }
|
||||||
expect(controller).to receive(:xmlrpc_get_users_blogs_enabled?).and_return(true)
|
|
||||||
|
|
||||||
expect(controller.target)
|
before { expect(xmlrpc).to receive(:enabled?).and_return(true) }
|
||||||
.to receive(:xmlrpc).and_return(WPScan::Model::XMLRPC.new("#{target_url}xmlrpc.php"))
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when WP version not found' do
|
context 'when wp.getUsersBlogs methods not available' do
|
||||||
it 'returns the XMLRPC' do
|
it 'returns the WpLogin' do
|
||||||
expect(controller.target).to receive(:wp_version).and_return(false)
|
expect(xmlrpc).to receive(:available_methods).and_return(%w[m1 m2])
|
||||||
|
|
||||||
expect(controller.attacker).to be_a WPScan::Finders::Passwords::XMLRPC
|
expect(controller.attacker).to be_a WPScan::Finders::Passwords::WpLogin
|
||||||
expect(controller.attacker.target).to be_a WPScan::Model::XMLRPC
|
expect(controller.attacker.target).to be_a WPScan::Target
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when WP version found' do
|
context 'when wp.getUsersBlogs method evailable' do
|
||||||
before { expect(controller.target).to receive(:wp_version).and_return(wp_version) }
|
before { expect(xmlrpc).to receive(:available_methods).and_return(%w[wp.getUsersBlogs m2]) }
|
||||||
|
|
||||||
context 'when WP < 4.4' do
|
context 'when WP version not found' do
|
||||||
let(:wp_version) { WPScan::Model::WpVersion.new('3.8.1') }
|
it 'returns the XMLRPC' do
|
||||||
|
expect(controller.target).to receive(:wp_version).and_return(false)
|
||||||
|
|
||||||
it 'returns the XMLRPCMulticall' do
|
expect(controller.attacker).to be_a WPScan::Finders::Passwords::XMLRPC
|
||||||
expect(controller.attacker).to be_a WPScan::Finders::Passwords::XMLRPCMulticall
|
|
||||||
expect(controller.attacker.target).to be_a WPScan::Model::XMLRPC
|
expect(controller.attacker.target).to be_a WPScan::Model::XMLRPC
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when WP >= 4.4' do
|
context 'when WP version found' do
|
||||||
let(:wp_version) { WPScan::Model::WpVersion.new('4.4') }
|
before { expect(controller.target).to receive(:wp_version).and_return(wp_version) }
|
||||||
|
|
||||||
it 'returns the XMLRPC' do
|
context 'when WP < 4.4' do
|
||||||
expect(controller.attacker).to be_a WPScan::Finders::Passwords::XMLRPC
|
let(:wp_version) { WPScan::Model::WpVersion.new('3.8.1') }
|
||||||
expect(controller.attacker.target).to be_a WPScan::Model::XMLRPC
|
|
||||||
|
it 'returns the XMLRPCMulticall' do
|
||||||
|
expect(controller.attacker).to be_a WPScan::Finders::Passwords::XMLRPCMulticall
|
||||||
|
expect(controller.attacker.target).to be_a WPScan::Model::XMLRPC
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when WP >= 4.4' do
|
||||||
|
let(:wp_version) { WPScan::Model::WpVersion.new('4.4') }
|
||||||
|
|
||||||
|
it 'returns the XMLRPC' do
|
||||||
|
expect(controller.attacker).to be_a WPScan::Finders::Passwords::XMLRPC
|
||||||
|
expect(controller.attacker.target).to be_a WPScan::Model::XMLRPC
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
# 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
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
describe WPScan::Finders::InterestingFindings::BackupDB do
|
|
||||||
subject(:finder) { described_class.new(target) }
|
|
||||||
let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) }
|
|
||||||
let(:url) { 'http://ex.lo/' }
|
|
||||||
let(:fixtures) { FINDERS_FIXTURES.join('interesting_findings', 'backup_db') }
|
|
||||||
let(:wp_content) { 'wp-content' }
|
|
||||||
let(:dir_url) { target.url("#{wp_content}/backup-db/") }
|
|
||||||
|
|
||||||
before do
|
|
||||||
expect(target).to receive(:content_dir).at_least(1).and_return(wp_content)
|
|
||||||
expect(target).to receive(:head_or_get_params).and_return(method: :head)
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#aggressive' do
|
|
||||||
context 'when not a 200 or 403' do
|
|
||||||
it 'returns nil' do
|
|
||||||
stub_request(:head, dir_url).to_return(status: 404)
|
|
||||||
|
|
||||||
expect(finder.aggressive).to eql nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when 200 and matching the homepage' do
|
|
||||||
it 'returns nil' do
|
|
||||||
stub_request(:head, dir_url)
|
|
||||||
stub_request(:get, dir_url)
|
|
||||||
|
|
||||||
expect(target).to receive(:homepage_or_404?).and_return(true)
|
|
||||||
|
|
||||||
expect(finder.aggressive).to eql nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when 200 or 403' do
|
|
||||||
before do
|
|
||||||
stub_request(:head, dir_url)
|
|
||||||
stub_request(:get, dir_url).and_return(body: body)
|
|
||||||
|
|
||||||
expect(target).to receive(:homepage_or_404?).and_return(false)
|
|
||||||
end
|
|
||||||
|
|
||||||
after do
|
|
||||||
found = finder.aggressive
|
|
||||||
|
|
||||||
expect(found).to eql WPScan::Model::BackupDB.new(
|
|
||||||
dir_url,
|
|
||||||
confidence: 70,
|
|
||||||
found_by: described_class::DIRECT_ACCESS
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(found.interesting_entries).to eq @expected_entries
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when no directory listing' do
|
|
||||||
let(:body) { '' }
|
|
||||||
|
|
||||||
it 'returns an empty interesting_findings attribute' do
|
|
||||||
@expected_entries = []
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when directory listing enabled' do
|
|
||||||
let(:body) { File.read(fixtures.join('dir_listing.html')) }
|
|
||||||
|
|
||||||
it 'returns the expected interesting_findings attribute' do
|
|
||||||
@expected_entries = %w[sqldump.sql test.txt]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
describe WPScan::Finders::InterestingFindings::PluginBackupFolders do
|
||||||
|
subject(:finder) { described_class.new(target) }
|
||||||
|
let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) }
|
||||||
|
let(:url) { 'http://ex.lo/' }
|
||||||
|
let(:fixtures) { FINDERS_FIXTURES.join('interesting_findings', 'plugin_backup_folders') }
|
||||||
|
|
||||||
|
before do
|
||||||
|
expect(target).to receive(:content_dir).at_least(1).and_return('wp-content')
|
||||||
|
|
||||||
|
finder.class::PATHS.each { |path| stub_request(:head, target.url(path)).to_return(status: 404) }
|
||||||
|
|
||||||
|
allow(target).to receive(:head_or_get_params).and_return(method: :head)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#aggressive' do
|
||||||
|
context 'when none of them exist' do
|
||||||
|
it 'returns an empty array' do
|
||||||
|
expect(finder.aggressive).to eql([])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when one exist but matches the homepage' do
|
||||||
|
let(:existing_url) { target.url(finder.class::PATHS.sample) }
|
||||||
|
|
||||||
|
it 'ignores it' do
|
||||||
|
stub_request(:head, existing_url)
|
||||||
|
stub_request(:get, existing_url)
|
||||||
|
|
||||||
|
expect(target).to receive(:homepage_or_404?).and_return(true)
|
||||||
|
|
||||||
|
expect(finder.aggressive).to eql([])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when 200 or 403' do
|
||||||
|
let(:existing_url) { target.url(finder.class::PATHS.sample) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_request(:head, existing_url)
|
||||||
|
stub_request(:get, existing_url).and_return(body: body)
|
||||||
|
|
||||||
|
expect(target).to receive(:homepage_or_404?).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
found = finder.aggressive
|
||||||
|
|
||||||
|
expect(found.size).to eql 1
|
||||||
|
|
||||||
|
expect(found).to eql([WPScan::Model::PluginBackupFolder.new(existing_url,
|
||||||
|
confidence: 70,
|
||||||
|
found_by: described_class::DIRECT_ACCESS)])
|
||||||
|
|
||||||
|
expect(found.first.interesting_entries).to eq @expected_entries
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when no directory listing' do
|
||||||
|
let(:body) { '' }
|
||||||
|
|
||||||
|
it 'returns an empty interesting_findings attribute' do
|
||||||
|
@expected_entries = []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when directory listing enabled' do
|
||||||
|
let(:body) { File.read(fixtures.join('dir_listing.html')) }
|
||||||
|
|
||||||
|
it 'returns the expected interesting_findings attribute' do
|
||||||
|
@expected_entries = %w[sqldump.sql test.txt]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
describe WPScan::Finders::Passwords::WpLogin do
|
|
||||||
subject(:finder) { described_class.new(target) }
|
|
||||||
let(:target) { WPScan::Target.new(url) }
|
|
||||||
let(:url) { 'http://ex.lo/' }
|
|
||||||
|
|
||||||
describe '#valid_credentials?' do
|
|
||||||
context 'when a non 302' do
|
|
||||||
it 'returns false' do
|
|
||||||
expect(finder.valid_credentials?(Typhoeus::Response.new(code: 200, headers: {}))).to be_falsey
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when a 302' do
|
|
||||||
let(:response) { Typhoeus::Response.new(code: 302, headers: headers) }
|
|
||||||
|
|
||||||
context 'when no cookies set' do
|
|
||||||
let(:headers) { {} }
|
|
||||||
|
|
||||||
it 'returns false' do
|
|
||||||
expect(finder.valid_credentials?(response)).to be_falsey
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when no logged_in cookie set' do
|
|
||||||
context 'when only one cookie set' do
|
|
||||||
let(:headers) { 'Set-Cookie: wordpress_test_cookie=WP+Cookie+check; path=/' }
|
|
||||||
|
|
||||||
it 'returns false' do
|
|
||||||
expect(finder.valid_credentials?(response)).to be_falsey
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when multiple cookies set' do
|
|
||||||
let(:headers) do
|
|
||||||
"Set-Cookie: wordpress_test_cookie=WP+Cookie+check; path=/\r\n" \
|
|
||||||
'Set-Cookie: something=value; path=/'
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns false' do
|
|
||||||
expect(finder.valid_credentials?(response)).to be_falsey
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when logged_in cookie set' do
|
|
||||||
let(:headers) do
|
|
||||||
"Set-Cookie: wordpress_test_cookie=WP+Cookie+check; path=/\r\r" \
|
|
||||||
"Set-Cookie: wordpress_xxx=yyy; path=/wp-content/plugins; httponly\r\n" \
|
|
||||||
"Set-Cookie: wordpress_xxx=yyy; path=/wp-admin; httponly\r\n" \
|
|
||||||
'Set-Cookie: wordpress_logged_in_xxx=yyy; path=/; httponly'
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns false' do
|
|
||||||
expect(finder.valid_credentials?(response)).to eql true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# If this file is tested alone (rspec path-to-this-file), then there will be an error about
|
||||||
|
# constants not being intilialized. This is due to the Dynamic Finders.
|
||||||
|
|
||||||
describe WPScan::Finders::PluginVersion::Base do
|
describe WPScan::Finders::PluginVersion::Base do
|
||||||
subject(:plugin_version) { described_class.new(plugin) }
|
subject(:plugin_version) { described_class.new(plugin) }
|
||||||
let(:plugin) { WPScan::Model::Plugin.new(slug, target) }
|
let(:plugin) { WPScan::Model::Plugin.new(slug, target) }
|
||||||
@@ -12,7 +15,7 @@ describe WPScan::Finders::PluginVersion::Base do
|
|||||||
expect(plugin_version.finders.map { |f| f.class.to_s.demodulize }).to match_array @expected
|
expect(plugin_version.finders.map { |f| f.class.to_s.demodulize }).to match_array @expected
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when no related dynamic finders' do
|
context 'when no related specific finders' do
|
||||||
let(:slug) { 'spec' }
|
let(:slug) { 'spec' }
|
||||||
|
|
||||||
it 'contains the default finders' do
|
it 'contains the default finders' do
|
||||||
@@ -22,13 +25,19 @@ describe WPScan::Finders::PluginVersion::Base do
|
|||||||
|
|
||||||
# Dynamic Version Finders are not tested here, they are in
|
# Dynamic Version Finders are not tested here, they are in
|
||||||
# spec/lib/finders/dynamic_finder/plugin_versions_spec
|
# spec/lib/finders/dynamic_finder/plugin_versions_spec
|
||||||
context 'when dynamic finders' do
|
context 'when specific finders' do
|
||||||
|
let(:specific) do
|
||||||
|
{
|
||||||
|
# None so far
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
WPScan::DB::DynamicFinders::Plugin.versions_finders_configs.each do |plugin_slug, configs|
|
WPScan::DB::DynamicFinders::Plugin.versions_finders_configs.each do |plugin_slug, configs|
|
||||||
context "when #{plugin_slug} plugin" do
|
context "when #{plugin_slug} plugin" do
|
||||||
let(:slug) { plugin_slug }
|
let(:slug) { plugin_slug }
|
||||||
|
|
||||||
it 'contains the expected finders (default + the dynamic ones)' do
|
it 'contains the expected finders (default + specific + the dynamic ones)' do
|
||||||
@expected = default_finders + configs.keys
|
@expected = default_finders + [*specific[plugin_slug]] + configs.keys
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -13,21 +13,20 @@ describe WPScan::Finders::ThemeVersion::Base do
|
|||||||
expect(theme_version.finders.map { |f| f.class.to_s.demodulize }).to eql @expected
|
expect(theme_version.finders.map { |f| f.class.to_s.demodulize }).to eql @expected
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when no related dynamic finders' do
|
context 'when no related specific finders' do
|
||||||
it 'contains the default finders' do
|
it 'contains the default finders' do
|
||||||
@expected = default_finders
|
@expected = default_finders
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Dynamic Version Finders are not tested here, they are in
|
context 'when specific finders' do
|
||||||
# spec/lib/finders/dynamic_finder/theme_versions_spec
|
{
|
||||||
context 'when dynamic finders' do
|
}.each do |theme_slug, specific_finders|
|
||||||
WPScan::DB::DynamicFinders::Theme.versions_finders_configs.each do |theme_slug, configs|
|
|
||||||
context "when #{theme_slug} theme" do
|
context "when #{theme_slug} theme" do
|
||||||
let(:slug) { theme_slug }
|
let(:slug) { theme_slug }
|
||||||
|
|
||||||
it 'contains the expected finders (default + the dynamic ones)' do
|
it 'contains the expected finders' do
|
||||||
@expected = default_finders + configs.keys
|
@expected = default_finders + specific_finders
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -60,60 +60,25 @@ describe WPScan::Model::Plugin do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'potential_readme_filenames' do
|
|
||||||
context 'when not set in the DF file' do
|
|
||||||
its(:potential_readme_filenames) { should eql described_class::READMES }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when set in the DF file' do
|
|
||||||
context 'as a string' do
|
|
||||||
let(:slug) { 'photoblocks-grid-gallery' }
|
|
||||||
|
|
||||||
its(:potential_readme_filenames) { should eql %w[README.txt] }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'as an array' do
|
|
||||||
let(:slug) { 'customerlabs-actionrecorder' }
|
|
||||||
|
|
||||||
its(:potential_readme_filenames) { should eql %w[Readme.txt Readme.md] }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#latest_version, #last_updated, #popular' do
|
describe '#latest_version, #last_updated, #popular' do
|
||||||
before { allow(plugin).to receive(:db_data).and_return(db_data) }
|
context 'when none' do
|
||||||
|
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 no db_data but metadata' do
|
context 'when values' 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' }
|
||||||
|
|
||||||
@@ -131,13 +96,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 < latest_version' do
|
context 'when version < last_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 >= latest_version' do
|
context 'when version >= last_version' do
|
||||||
let(:version_number) { '3.0' }
|
let(:version_number) { '3.0' }
|
||||||
|
|
||||||
its(:outdated?) { should eql false }
|
its(:outdated?) { should eql false }
|
||||||
@@ -145,7 +110,7 @@ describe WPScan::Model::Plugin do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when no latest_version' do
|
context 'when no last_version' do
|
||||||
let(:slug) { 'vulnerable-not-popular' }
|
let(:slug) { 'vulnerable-not-popular' }
|
||||||
|
|
||||||
context 'when no version' do
|
context 'when no version' do
|
||||||
@@ -168,16 +133,13 @@ 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 = []
|
||||||
@@ -186,8 +148,7 @@ 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 = []
|
||||||
@@ -195,13 +156,11 @@ 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 <= 6.3.10 - LFI',
|
'First Vuln',
|
||||||
{ wpvulndb: '1' },
|
{ wpvulndb: '1' },
|
||||||
'LFI',
|
'LFI',
|
||||||
'6.3.10'
|
'6.3.10'
|
||||||
|
|||||||
@@ -86,179 +86,8 @@ 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
|
||||||
before do
|
xit
|
||||||
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,13 +40,11 @@ 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 be_empty }
|
its(:vulnerabilities) { should eql([]) }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when vulnerable' do
|
context 'when vulnerable' do
|
||||||
@@ -55,30 +53,13 @@ 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.1' }
|
let(:number) { '3.8' }
|
||||||
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(
|
||||||
'WP 3.8 - Vuln 1',
|
'WP 3.8 - Vuln 1',
|
||||||
{ url: %w[url-4], wpvulndb: '3' },
|
{ url: %w[url-4], osvdb: %w[11], wpvulndb: '3' },
|
||||||
'AUTHBYPASS'
|
'AUTHBYPASS'
|
||||||
)]
|
)]
|
||||||
end
|
end
|
||||||
@@ -86,7 +67,6 @@ 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 = [
|
||||||
@@ -97,7 +77,7 @@ describe WPScan::Model::WpVersion do
|
|||||||
),
|
),
|
||||||
WPScan::Vulnerability.new(
|
WPScan::Vulnerability.new(
|
||||||
'WP 3.8.1 - Vuln 2',
|
'WP 3.8.1 - Vuln 2',
|
||||||
{ url: %w[url-2 url-3], cve: %w[2014-0166], wpvulndb: '2' },
|
{ url: %w[url-2 url-3], osvdb: %w[10], cve: %w[2014-0166], wpvulndb: '2' },
|
||||||
nil,
|
nil,
|
||||||
'3.8.2'
|
'3.8.2'
|
||||||
)
|
)
|
||||||
@@ -107,30 +87,27 @@ describe WPScan::Model::WpVersion do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#metadata, #release_date, #status' do
|
describe '#release_date' do
|
||||||
subject(:version) { described_class.new('3.8.1') }
|
subject(:version) { described_class.new('3.8.1') }
|
||||||
|
|
||||||
before { allow(version).to receive(:db_data).and_return(db_data) }
|
its(:release_date) { should eql '2014-01-23' }
|
||||||
|
|
||||||
context 'when no db_data' do
|
context 'when the version is not in the DB' do
|
||||||
let(:db_data) { {} }
|
subject(:version) { described_class.new('3.8.2') }
|
||||||
|
|
||||||
its(:release_date) { should eql '2014-01-23' }
|
its(:release_date) { should eql 'Unknown' }
|
||||||
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
|
||||||
|
|
||||||
context 'when db_data' do
|
describe '#status' do
|
||||||
let(:db_data) { vuln_api_data_for('wordpresses/381') }
|
subject(:version) { described_class.new('3.8.1') }
|
||||||
|
|
||||||
its(:release_date) { should eql '2014-01-23-via-api' }
|
its(:status) { should eql 'outdated' }
|
||||||
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,7 +9,6 @@ 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'
|
||||||
|
|||||||
10200
spec/fixtures/db/dynamic_finders.yml
vendored
10200
spec/fixtures/db/dynamic_finders.yml
vendored
File diff suppressed because it is too large
Load Diff
56
spec/fixtures/db/metadata.json
vendored
56
spec/fixtures/db/metadata.json
vendored
@@ -1,56 +0,0 @@
|
|||||||
{
|
|
||||||
"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
Normal file
25
spec/fixtures/db/plugins.json
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
1
spec/fixtures/db/sponsor.txt
vendored
1
spec/fixtures/db/sponsor.txt
vendored
@@ -1 +0,0 @@
|
|||||||
Sponsored By Kittens
|
|
||||||
48
spec/fixtures/db/themes.json
vendored
Normal file
48
spec/fixtures/db/themes.json
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user