Compare commits
107 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9a5bc66df | ||
|
|
edebc77726 | ||
|
|
271dee824d | ||
|
|
1e868d10ca | ||
|
|
4be3f17ae4 | ||
|
|
f24e7be264 | ||
|
|
9adc26445d | ||
|
|
353e7dcbb9 | ||
|
|
430e65c12e | ||
|
|
1aa242a9d8 | ||
|
|
7173cd85fe | ||
|
|
b95a4f55e3 | ||
|
|
6b5e016770 | ||
|
|
85aa9f61cd | ||
|
|
5c187002d6 | ||
|
|
9bc373308b | ||
|
|
cdeb0fc144 | ||
|
|
f1acdd9389 | ||
|
|
d6fac6a210 | ||
|
|
007cfb0801 | ||
|
|
1f9829b7c0 | ||
|
|
e039d22565 | ||
|
|
b0775b1610 | ||
|
|
0e429700c6 | ||
|
|
af7804ca23 | ||
|
|
9da326967b | ||
|
|
62600b3a66 | ||
|
|
b236138fb5 | ||
|
|
40c2e9a54b | ||
|
|
a9062db57f | ||
|
|
2621404c5f | ||
|
|
c47211ca79 | ||
|
|
e39a192e8d | ||
|
|
d85035d5ef | ||
|
|
de09a97343 | ||
|
|
a6855345d7 | ||
|
|
a53f88b626 | ||
|
|
7048c82124 | ||
|
|
6aa7cda478 | ||
|
|
ff339b9a8c | ||
|
|
8898cc20fe | ||
|
|
770d1da280 | ||
|
|
6ba4e8a29b | ||
|
|
953ca68495 | ||
|
|
4289dfb37d | ||
|
|
4f6f2f436a | ||
|
|
237979a479 | ||
|
|
2e48968fd3 | ||
|
|
9a0c4a5c8f | ||
|
|
9a011f0007 | ||
|
|
3f907a706f | ||
|
|
9446141716 | ||
|
|
1994826af8 | ||
|
|
ab950d6ffc | ||
|
|
b77e611a90 | ||
|
|
86f0284894 | ||
|
|
9bbe014dfe | ||
|
|
ad92c95500 | ||
|
|
d360190382 | ||
|
|
1737c8a7f6 | ||
|
|
cde262fd66 | ||
|
|
bd74689079 | ||
|
|
248942bdea | ||
|
|
d9f203300b | ||
|
|
aceabc969f | ||
|
|
dedc24d3a7 | ||
|
|
6e583e78e8 | ||
|
|
c012e83355 | ||
|
|
264355d185 | ||
|
|
fdbfd1ec60 | ||
|
|
7a8b27a255 | ||
|
|
ec4bfac98b | ||
|
|
c63ffe37c9 | ||
|
|
d2f3ce82c9 | ||
|
|
3e24a0b0a4 | ||
|
|
1a07e29ff4 | ||
|
|
1aa46a8928 | ||
|
|
d9083f8b5f | ||
|
|
23d558a6d7 | ||
|
|
665a5b7b12 | ||
|
|
1d73418969 | ||
|
|
f67b5e4cc4 | ||
|
|
ae2515444f | ||
|
|
463e77f0a5 | ||
|
|
d7b796b1a7 | ||
|
|
9b07d53077 | ||
|
|
8ee9b2bc31 | ||
|
|
c5989477a4 | ||
|
|
96d8a4e4f8 | ||
|
|
e865e11731 | ||
|
|
f0997bfe0d | ||
|
|
8b67dad456 | ||
|
|
53fdac1038 | ||
|
|
534a7602e6 | ||
|
|
30f329fe43 | ||
|
|
4ce39951a9 | ||
|
|
0e9eb34626 | ||
|
|
0ff299c425 | ||
|
|
6366258ce9 | ||
|
|
bca69a026e | ||
|
|
adc26ea42a | ||
|
|
84422b10c8 | ||
|
|
d05ad0f8f4 | ||
|
|
3f70ddaffa | ||
|
|
b16e8d84d7 | ||
|
|
5ee405d5a0 | ||
|
|
a5b9470636 |
19
.rubocop.yml
19
.rubocop.yml
@@ -4,14 +4,6 @@ AllCops:
|
||||
Exclude:
|
||||
- '*.gemspec'
|
||||
- 'vendor/**/*'
|
||||
ClassVars:
|
||||
Enabled: false
|
||||
LineLength:
|
||||
Max: 120
|
||||
MethodLength:
|
||||
Max: 20
|
||||
Exclude:
|
||||
- 'app/controllers/enumeration/cli_options.rb'
|
||||
Lint/UriEscapeUnescape:
|
||||
Enabled: false
|
||||
Metrics/AbcSize:
|
||||
@@ -25,7 +17,18 @@ Metrics/ClassLength:
|
||||
- 'app/controllers/enumeration/cli_options.rb'
|
||||
Metrics/CyclomaticComplexity:
|
||||
Max: 8
|
||||
Metrics/LineLength:
|
||||
Max: 120
|
||||
Metrics/MethodLength:
|
||||
Max: 20
|
||||
Exclude:
|
||||
- 'app/controllers/enumeration/cli_options.rb'
|
||||
Style/ClassVars:
|
||||
Enabled: false
|
||||
Style/Documentation:
|
||||
Enabled: false
|
||||
Style/FormatStringToken:
|
||||
Enabled: false
|
||||
Style/NumericPredicate:
|
||||
Exclude:
|
||||
- 'app/controllers/vuln_api.rb'
|
||||
|
||||
23
.travis.yml
23
.travis.yml
@@ -2,29 +2,12 @@ language: ruby
|
||||
sudo: false
|
||||
cache: bundler
|
||||
rvm:
|
||||
- 2.4.1
|
||||
- 2.4.2
|
||||
- 2.4.3
|
||||
- 2.4.4
|
||||
- 2.4.5
|
||||
- 2.4.6
|
||||
- 2.5.0
|
||||
- 2.5.1
|
||||
- 2.5.2
|
||||
- 2.5.3
|
||||
- 2.5.4
|
||||
- 2.5.5
|
||||
- 2.6.0
|
||||
- 2.6.1
|
||||
- 2.6.2
|
||||
- 2.6.3
|
||||
- ruby-head
|
||||
- 2.4.9
|
||||
- 2.5.7
|
||||
- 2.6.5
|
||||
before_install:
|
||||
- "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"
|
||||
- gem update --system
|
||||
matrix:
|
||||
allow_failures:
|
||||
- rvm: ruby-head
|
||||
script:
|
||||
- bundle exec rubocop
|
||||
- bundle exec rspec
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ruby:2.6.2-alpine3.9 AS builder
|
||||
FROM ruby:2.6.3-alpine AS builder
|
||||
LABEL maintainer="WPScan Team <team@wpscan.org>"
|
||||
|
||||
ARG BUNDLER_ARGS="--jobs=8 --without test development"
|
||||
@@ -19,7 +19,7 @@ RUN rake install --trace
|
||||
RUN chmod -R a+r /usr/local/bundle
|
||||
|
||||
|
||||
FROM ruby:2.6.2-alpine3.9
|
||||
FROM ruby:2.6.3-alpine
|
||||
LABEL maintainer="WPScan Team <team@wpscan.org>"
|
||||
|
||||
RUN adduser -h /wpscan -g WPScan -D wpscan
|
||||
@@ -38,4 +38,3 @@ USER wpscan
|
||||
RUN /usr/local/bundle/bin/wpscan --update --verbose
|
||||
|
||||
ENTRYPOINT ["/usr/local/bundle/bin/wpscan"]
|
||||
CMD ["--help"]
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -29,8 +29,6 @@ Example cases which do not require a commercial license, and thus fall under the
|
||||
|
||||
If you need to purchase a commercial license or are unsure whether you need to purchase a commercial license contact us - team@wpscan.org.
|
||||
|
||||
We may grant commercial licenses at no monetary cost at our own discretion if the commercial usage is deemed by the WPScan Team to significantly benefit WPScan.
|
||||
|
||||
Free-use Terms and Conditions;
|
||||
|
||||
3. Redistribution
|
||||
|
||||
46
README.md
46
README.md
@@ -17,7 +17,6 @@
|
||||
<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://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>
|
||||
|
||||
# INSTALL
|
||||
@@ -78,41 +77,60 @@ docker run -it --rm wpscanteam/wpscan --url https://target.tld/ --enumerate u1-1
|
||||
|
||||
# Usage
|
||||
|
||||
```wpscan --url blog.tld``` This will scan the blog using default options with a good compromise between speed and accuracy. For example, the plugins will be checked passively but their version with a mixed detection mode (passively + aggressively). Potential config backup files will also be checked, along with other interesting findings. If a more stealthy approach is required, then ```wpscan --stealthy --url blog.tld``` can be used.
|
||||
```wpscan --url blog.tld``` This will scan the blog using default options with a good compromise between speed and accuracy. For example, the plugins will be checked passively but their version with a mixed detection mode (passively + aggressively). Potential config backup files will also be checked, along with other interesting findings.
|
||||
|
||||
If a more stealthy approach is required, then ```wpscan --stealthy --url blog.tld``` can be used.
|
||||
As a result, when using the ```--enumerate``` option, don't forget to set the ```--plugins-detection``` accordingly, as its default is 'passive'.
|
||||
|
||||
For more options, open a terminal and type ```wpscan --help``` (if you built wpscan from the source, you should type the command outside of the git repo)
|
||||
|
||||
The DB is located at ~/.wpscan/db
|
||||
|
||||
## Vulnerability Database
|
||||
|
||||
The WPScan CLI tool uses the [WPVulnDB API](https://wpvulndb.com/api) to retrieve WordPress vulnerability data in real time. For WPScan to retrieve the vulnerability data an API token must be supplied via the `--api-token` option, or via a configuration file, as discussed below. An API token can be obtained by registering an account on [WPVulnDB](https://wpvulndb.com/users/sign_up). Up to 50 API requests per day are given free of charge to registered users. Once the 50 API requests are exhausted, WPScan will continue to work as normal but without any vulnerability data. Users can upgrade to paid API usage to increase their API limits within their user profile on [WPVulnDB](https://wpvulndb.com/).
|
||||
|
||||
## 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/cli_options.json
|
||||
- ~/.wpscan/cli_options.yml
|
||||
- pwd/.wpscan/cli_options.json
|
||||
- pwd/.wpscan/cli_options.yml
|
||||
- ~/.wpscan/scan.json
|
||||
- ~/.wpscan/scan.yml
|
||||
- pwd/.wpscan/scan.json
|
||||
- pwd/.wpscan/scan.yml
|
||||
|
||||
If those files exist, options from them will be loaded and overridden if found twice.
|
||||
If those files exist, options from the `cli_options` key will be loaded and overridden if found twice.
|
||||
|
||||
e.g:
|
||||
|
||||
~/.wpscan/cli_options.yml:
|
||||
~/.wpscan/scan.yml:
|
||||
|
||||
```yml
|
||||
proxy: 'http://127.0.0.1:8080'
|
||||
verbose: true
|
||||
cli_options:
|
||||
proxy: 'http://127.0.0.1:8080'
|
||||
verbose: true
|
||||
```
|
||||
|
||||
pwd/.wpscan/cli_options.yml:
|
||||
pwd/.wpscan/scan.yml:
|
||||
|
||||
```yml
|
||||
proxy: 'socks5://127.0.0.1:9090'
|
||||
url: 'http://target.tld'
|
||||
cli_options:
|
||||
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```
|
||||
|
||||
Enumerating usernames
|
||||
## 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
|
||||
|
||||
```shell
|
||||
wpscan --url https://target.tld/ --enumerate u
|
||||
|
||||
6
Rakefile
6
Rakefile
@@ -6,14 +6,18 @@ exec = []
|
||||
|
||||
begin
|
||||
require 'rubocop/rake_task'
|
||||
|
||||
RuboCop::RakeTask.new
|
||||
|
||||
exec << :rubocop
|
||||
rescue LoadError
|
||||
end
|
||||
|
||||
begin
|
||||
require 'rspec/core/rake_task'
|
||||
RSpec::Core::RakeTask.new(:spec)
|
||||
|
||||
RSpec::Core::RakeTask.new(:spec) { |t| t.rspec_opts = %w{--tag ~slow} }
|
||||
|
||||
exec << :spec
|
||||
rescue LoadError
|
||||
end
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'controllers/core'
|
||||
require_relative 'controllers/vuln_api'
|
||||
require_relative 'controllers/custom_directories'
|
||||
require_relative 'controllers/wp_version'
|
||||
require_relative 'controllers/main_theme'
|
||||
|
||||
@@ -18,7 +18,7 @@ module WPScan
|
||||
target.content_dir = ParsedCli.wp_content_dir if ParsedCli.wp_content_dir
|
||||
target.plugins_dir = ParsedCli.wp_plugins_dir if ParsedCli.wp_plugins_dir
|
||||
|
||||
return if target.content_dir(ParsedCli.detection_mode)
|
||||
return if target.content_dir
|
||||
|
||||
raise Error::WpContentDirNotDetected
|
||||
end
|
||||
|
||||
@@ -18,10 +18,10 @@ module WPScan
|
||||
choices: {
|
||||
vp: OptBoolean.new(['--vulnerable-plugins']),
|
||||
ap: OptBoolean.new(['--all-plugins']),
|
||||
p: OptBoolean.new(['--plugins']),
|
||||
p: OptBoolean.new(['--popular-plugins']),
|
||||
vt: OptBoolean.new(['--vulnerable-themes']),
|
||||
at: OptBoolean.new(['--all-themes']),
|
||||
t: OptBoolean.new(['--themes']),
|
||||
t: OptBoolean.new(['--popular-themes']),
|
||||
tt: OptBoolean.new(['--timthumbs']),
|
||||
cb: OptBoolean.new(['--config-backups']),
|
||||
dbe: OptBoolean.new(['--db-exports']),
|
||||
@@ -69,7 +69,7 @@ module WPScan
|
||||
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
|
||||
'Set to 0 to ignore the threshold.'], default: 100, advanced: true
|
||||
)
|
||||
]
|
||||
end
|
||||
@@ -98,7 +98,7 @@ module WPScan
|
||||
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
|
||||
'Set to 0 to ignore the threshold.'], default: 20, advanced: true
|
||||
)
|
||||
]
|
||||
end
|
||||
|
||||
@@ -56,7 +56,7 @@ module WPScan
|
||||
#
|
||||
# @return [ Boolean ] Wether or not to enumerate the plugins
|
||||
def enum_plugins?(opts)
|
||||
opts[:plugins] || opts[:all_plugins] || opts[:vulnerable_plugins]
|
||||
opts[:popular_plugins] || opts[:all_plugins] || opts[:vulnerable_plugins]
|
||||
end
|
||||
|
||||
def enum_plugins
|
||||
@@ -92,7 +92,7 @@ module WPScan
|
||||
|
||||
if opts[:enumerate][:all_plugins]
|
||||
DB::Plugins.all_slugs
|
||||
elsif opts[:enumerate][:plugins]
|
||||
elsif opts[:enumerate][:popular_plugins]
|
||||
DB::Plugins.popular_slugs
|
||||
else
|
||||
DB::Plugins.vulnerable_slugs
|
||||
@@ -103,7 +103,7 @@ module WPScan
|
||||
#
|
||||
# @return [ Boolean ] Wether or not to enumerate the themes
|
||||
def enum_themes?(opts)
|
||||
opts[:themes] || opts[:all_themes] || opts[:vulnerable_themes]
|
||||
opts[:popular_themes] || opts[:all_themes] || opts[:vulnerable_themes]
|
||||
end
|
||||
|
||||
def enum_themes
|
||||
@@ -139,7 +139,7 @@ module WPScan
|
||||
|
||||
if opts[:enumerate][:all_themes]
|
||||
DB::Themes.all_slugs
|
||||
elsif opts[:enumerate][:themes]
|
||||
elsif opts[:enumerate][:popular_themes]
|
||||
DB::Themes.popular_slugs
|
||||
else
|
||||
DB::Themes.vulnerable_slugs
|
||||
|
||||
30
app/controllers/vuln_api.rb
Normal file
30
app/controllers/vuln_api.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Controller
|
||||
# Controller to handle the API token
|
||||
class VulnApi < CMSScanner::Controller::Base
|
||||
def cli_options
|
||||
[
|
||||
OptString.new(['--api-token TOKEN', 'The WPVulnDB API Token to display vulnerability data'])
|
||||
]
|
||||
end
|
||||
|
||||
def before_scan
|
||||
return unless ParsedCli.api_token
|
||||
|
||||
DB::VulnApi.token = ParsedCli.api_token
|
||||
|
||||
api_status = DB::VulnApi.status
|
||||
|
||||
raise Error::InvalidApiToken if api_status['error']
|
||||
raise Error::ApiLimitReached if api_status['requests_remaining'] == 0
|
||||
raise api_status['http_error'] if api_status['http_error']
|
||||
end
|
||||
|
||||
def after_scan
|
||||
output('status', status: DB::VulnApi.status, api_requests: WPScan.api_requests)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -4,7 +4,6 @@ module WPScan
|
||||
module Finders
|
||||
module DbExports
|
||||
# DB Exports finder
|
||||
# See https://github.com/wpscanteam/wpscan-v3/issues/62
|
||||
class KnownLocations < CMSScanner::Finders::Finder
|
||||
include CMSScanner::Finders::Finder::Enumerator
|
||||
|
||||
@@ -20,9 +19,9 @@ module WPScan
|
||||
|
||||
enumerate(potential_urls(opts), opts.merge(check_full_response: 200)) do |res|
|
||||
if res.effective_url.end_with?('.zip')
|
||||
next unless res.headers['Content-Type'] =~ %r{\Aapplication/zip}i
|
||||
next unless %r{\Aapplication/zip}i.match?(res.headers['Content-Type'])
|
||||
else
|
||||
next unless res.body =~ SQL_PATTERN
|
||||
next unless SQL_PATTERN.match?(res.body)
|
||||
end
|
||||
|
||||
found << Model::DbExport.new(res.request.url, found_by: DIRECT_ACCESS, confidence: 100)
|
||||
@@ -41,7 +40,7 @@ module WPScan
|
||||
# @return [ Hash ]
|
||||
def potential_urls(opts = {})
|
||||
urls = {}
|
||||
domain_name = target.uri.host[/(^[\w|-]+)/, 1]
|
||||
domain_name = PublicSuffix.domain(target.uri.host)[/(^[\w|-]+)/, 1]
|
||||
|
||||
File.open(opts[:list]).each_with_index do |path, index|
|
||||
path.gsub!('{domain_name}', domain_name)
|
||||
|
||||
@@ -9,7 +9,7 @@ module WPScan
|
||||
def aggressive(_opts = {})
|
||||
path = 'installer-log.txt'
|
||||
|
||||
return unless target.head_and_get(path).body =~ /DUPLICATOR INSTALL-LOG/
|
||||
return unless /DUPLICATOR INSTALL-LOG/.match?(target.head_and_get(path).body)
|
||||
|
||||
Model::DuplicatorInstallerLog.new(
|
||||
target.url(path),
|
||||
|
||||
@@ -10,7 +10,7 @@ module WPScan
|
||||
pattern = %r{#{target.content_dir}/mu\-plugins/}i
|
||||
|
||||
target.in_scope_uris(target.homepage_res) do |uri|
|
||||
next unless uri.path =~ pattern
|
||||
next unless uri.path&.match?(pattern)
|
||||
|
||||
url = target.url('wp-content/mu-plugins/')
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ module WPScan
|
||||
path = 'wp-content/uploads/dump.sql'
|
||||
res = target.head_and_get(path, [200], get: { headers: { 'Range' => 'bytes=0-3000' } })
|
||||
|
||||
return unless res.body =~ SQL_PATTERN
|
||||
return unless SQL_PATTERN.match?(res.body)
|
||||
|
||||
Model::UploadSQLDump.new(
|
||||
target.url(path),
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'main_theme/css_style'
|
||||
require_relative 'main_theme/css_style_in_homepage'
|
||||
require_relative 'main_theme/css_style_in_404_page'
|
||||
require_relative 'main_theme/woo_framework_meta_generator'
|
||||
require_relative 'main_theme/urls_in_homepage'
|
||||
require_relative 'main_theme/urls_in_404_page'
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
@@ -14,9 +16,11 @@ module WPScan
|
||||
# @param [ WPScan::Target ] target
|
||||
def initialize(target)
|
||||
finders <<
|
||||
MainTheme::CssStyle.new(target) <<
|
||||
MainTheme::CssStyleInHomepage.new(target) <<
|
||||
MainTheme::CssStyleIn404Page.new(target) <<
|
||||
MainTheme::WooFrameworkMetaGenerator.new(target) <<
|
||||
MainTheme::UrlsInHomepage.new(target)
|
||||
MainTheme::UrlsInHomepage.new(target) <<
|
||||
MainTheme::UrlsIn404Page.new(target)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
14
app/finders/main_theme/css_style_in_404_page.rb
Normal file
14
app/finders/main_theme/css_style_in_404_page.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module MainTheme
|
||||
# From the CSS style in the 404 page
|
||||
class CssStyleIn404Page < CssStyleInHomepage
|
||||
def passive(opts = {})
|
||||
passive_from_css_href(target.error_404_res, opts) || passive_from_style_code(target.error_404_res, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -3,9 +3,9 @@
|
||||
module WPScan
|
||||
module Finders
|
||||
module MainTheme
|
||||
# From the css style
|
||||
class CssStyle < CMSScanner::Finders::Finder
|
||||
include Finders::WpItems::URLsInHomepage
|
||||
# From the CSS style in the homepage
|
||||
class CssStyleInHomepage < CMSScanner::Finders::Finder
|
||||
include Finders::WpItems::UrlsInPage # To have the item_code_pattern method available here
|
||||
|
||||
def create_theme(slug, style_url, opts)
|
||||
Model::Theme.new(
|
||||
15
app/finders/main_theme/urls_in_404_page.rb
Normal file
15
app/finders/main_theme/urls_in_404_page.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module MainTheme
|
||||
# URLs In 404 Page Finder
|
||||
class UrlsIn404Page < UrlsInHomepage
|
||||
# @return [ Typhoeus::Response ]
|
||||
def page_res
|
||||
@page_res ||= target.error_404_res
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,7 +5,7 @@ module WPScan
|
||||
module MainTheme
|
||||
# URLs In Homepage Finder
|
||||
class UrlsInHomepage < CMSScanner::Finders::Finder
|
||||
include WpItems::URLsInHomepage
|
||||
include WpItems::UrlsInPage
|
||||
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
@@ -21,6 +21,11 @@ module WPScan
|
||||
|
||||
found
|
||||
end
|
||||
|
||||
# @return [ Typhoeus::Response ]
|
||||
def page_res
|
||||
@page_res ||= target.homepage_res
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,7 +10,7 @@ module WPScan
|
||||
PATTERN = /#{THEME_PATTERN}\s+#{FRAMEWORK_PATTERN}/i.freeze
|
||||
|
||||
def passive(opts = {})
|
||||
return unless target.homepage_res.body =~ PATTERN
|
||||
return unless target.homepage_res.body =~ PATTERN || target.error_404_res.body =~ PATTERN
|
||||
|
||||
Model::Theme.new(
|
||||
Regexp.last_match[1],
|
||||
|
||||
@@ -13,7 +13,7 @@ module WPScan
|
||||
|
||||
def valid_credentials?(response)
|
||||
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
|
||||
|
||||
def errored_response?(response)
|
||||
|
||||
@@ -8,7 +8,7 @@ module WPScan
|
||||
include CMSScanner::Finders::Finder::BreadthFirstDictionaryAttack
|
||||
|
||||
def login_request(username, password)
|
||||
target.method_call('wp.getUsersBlogs', [username, password])
|
||||
target.method_call('wp.getUsersBlogs', [username, password], cache_ttl: 0)
|
||||
end
|
||||
|
||||
def valid_credentials?(response)
|
||||
|
||||
@@ -19,7 +19,7 @@ module WPScan
|
||||
end
|
||||
end
|
||||
|
||||
target.multi_call(methods).run
|
||||
target.multi_call(methods, cache_ttl: 0).run
|
||||
end
|
||||
|
||||
# @param [ Array<Model::User> ] users
|
||||
|
||||
@@ -52,7 +52,7 @@ module WPScan
|
||||
|
||||
number = Regexp.last_match[1]
|
||||
|
||||
number if number =~ /[0-9]+/
|
||||
number if /[0-9]+/.match?(number)
|
||||
end
|
||||
|
||||
# @param [ String ] body
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'plugins/urls_in_homepage'
|
||||
require_relative 'plugins/urls_in_404_page'
|
||||
require_relative 'plugins/known_locations'
|
||||
# From the DynamicFinders
|
||||
require_relative 'plugins/comment'
|
||||
@@ -22,6 +23,7 @@ module WPScan
|
||||
def initialize(target)
|
||||
finders <<
|
||||
Plugins::UrlsInHomepage.new(target) <<
|
||||
Plugins::UrlsIn404Page.new(target) <<
|
||||
Plugins::HeaderPattern.new(target) <<
|
||||
Plugins::Comment.new(target) <<
|
||||
Plugins::Xpath.new(target) <<
|
||||
|
||||
@@ -15,7 +15,7 @@ module WPScan
|
||||
#
|
||||
# @return [ Plugin ] The detected plugin in the response, related to the config
|
||||
def process_response(opts, response, slug, klass, config)
|
||||
return unless response.body =~ config['pattern']
|
||||
return unless response.body&.match?(config['pattern'])
|
||||
|
||||
Model::Plugin.new(
|
||||
slug,
|
||||
|
||||
@@ -18,7 +18,7 @@ module WPScan
|
||||
response.html.xpath(config['xpath'] || '//comment()').each do |node|
|
||||
comment = node.text.to_s.strip
|
||||
|
||||
next unless comment =~ config['pattern']
|
||||
next unless comment&.match?(config['pattern'])
|
||||
|
||||
return Model::Plugin.new(
|
||||
slug,
|
||||
|
||||
16
app/finders/plugins/urls_in_404_page.rb
Normal file
16
app/finders/plugins/urls_in_404_page.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Plugins
|
||||
# URLs In 404 Page Finder
|
||||
# Typically, the items detected from URLs like /wp-content/plugins/<slug>/
|
||||
class UrlsIn404Page < UrlsInHomepage
|
||||
# @return [ Typhoeus::Response ]
|
||||
def page_res
|
||||
@page_res ||= target.error_404_res
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -4,10 +4,9 @@ module WPScan
|
||||
module Finders
|
||||
module Plugins
|
||||
# URLs In Homepage Finder
|
||||
# Typically, the items detected from URLs like
|
||||
# /wp-content/plugins/<slug>/
|
||||
# Typically, the items detected from URLs like /wp-content/plugins/<slug>/
|
||||
class UrlsInHomepage < CMSScanner::Finders::Finder
|
||||
include WpItems::URLsInHomepage
|
||||
include WpItems::UrlsInPage
|
||||
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
@@ -21,6 +20,11 @@ module WPScan
|
||||
|
||||
found
|
||||
end
|
||||
|
||||
# @return [ Typhoeus::Response ]
|
||||
def page_res
|
||||
@page_res ||= target.homepage_res
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'themes/urls_in_homepage'
|
||||
require_relative 'themes/urls_in_404_page'
|
||||
require_relative 'themes/known_locations'
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Themes
|
||||
# themes Finder
|
||||
# Themes Finder
|
||||
class Base
|
||||
include CMSScanner::Finders::SameTypeFinder
|
||||
|
||||
@@ -14,6 +15,7 @@ module WPScan
|
||||
def initialize(target)
|
||||
finders <<
|
||||
Themes::UrlsInHomepage.new(target) <<
|
||||
Themes::UrlsIn404Page.new(target) <<
|
||||
Themes::KnownLocations.new(target)
|
||||
end
|
||||
end
|
||||
|
||||
15
app/finders/themes/urls_in_404_page.rb
Normal file
15
app/finders/themes/urls_in_404_page.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Themes
|
||||
# URLs In 04 Page Finder
|
||||
class UrlsIn404Page < UrlsInHomepage
|
||||
# @return [ Typhoeus::Response ]
|
||||
def page_res
|
||||
@page_res ||= target.error_404_res
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,7 +5,7 @@ module WPScan
|
||||
module Themes
|
||||
# URLs In Homepage Finder
|
||||
class UrlsInHomepage < CMSScanner::Finders::Finder
|
||||
include WpItems::URLsInHomepage
|
||||
include WpItems::UrlsInPage
|
||||
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
@@ -19,6 +19,11 @@ module WPScan
|
||||
|
||||
found
|
||||
end
|
||||
|
||||
# @return [ Typhoeus::Response ]
|
||||
def page_res
|
||||
@page_res ||= target.homepage_res
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -22,7 +22,7 @@ module WPScan
|
||||
found = []
|
||||
|
||||
enumerate(target_urls(opts), opts.merge(check_full_response: 400)) do |res|
|
||||
next unless res.body =~ /no image specified/i
|
||||
next unless /no image specified/i.match?(res.body)
|
||||
|
||||
found << Model::Timthumb.new(res.request.url, opts.merge(found_by: found_by, confidence: 100))
|
||||
end
|
||||
|
||||
@@ -97,9 +97,12 @@ module WPScan
|
||||
# @return [ String, nil ]
|
||||
def display_name_from_body(body)
|
||||
page = Nokogiri::HTML.parse(body)
|
||||
|
||||
# WP >= 3.0
|
||||
page.css('h1.page-title span').each do |node|
|
||||
return node.text.to_s
|
||||
text = node.text.to_s.strip
|
||||
|
||||
return text unless text.empty?
|
||||
end
|
||||
|
||||
# WP < 3.0
|
||||
|
||||
@@ -24,7 +24,7 @@ module WPScan
|
||||
|
||||
return found if error.empty? # Protection plugin / error disabled
|
||||
|
||||
next unless error =~ /The password you entered for the username|Incorrect Password/i
|
||||
next unless /The password you entered for the username|Incorrect Password/i.match?(error)
|
||||
|
||||
found << Model::User.new(username, found_by: found_by, confidence: 100)
|
||||
end
|
||||
|
||||
@@ -34,6 +34,8 @@ module WPScan
|
||||
def user_details_from_oembed_data(oembed_data)
|
||||
return unless oembed_data
|
||||
|
||||
oembed_data = oembed_data.first if oembed_data.is_a?(Array)
|
||||
|
||||
if oembed_data['author_url'] =~ %r{/author/([^/]+)/?\z}
|
||||
details = [Regexp.last_match[1], 'Author URL', 90]
|
||||
elsif oembed_data['author_name'] && !oembed_data['author_name'].empty?
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'wp_items/urls_in_homepage'
|
||||
require_relative 'wp_items/urls_in_page'
|
||||
|
||||
@@ -4,7 +4,7 @@ module WPScan
|
||||
module Finders
|
||||
module WpItems
|
||||
# URLs In Homepage Module to use in plugins & themes finders
|
||||
module URLsInHomepage
|
||||
module UrlsInPage
|
||||
# @param [ String ] type plugins / themes
|
||||
# @param [ Boolean ] uniq Wether or not to apply the #uniq on the results
|
||||
#
|
||||
@@ -12,10 +12,12 @@ module WPScan
|
||||
def items_from_links(type, uniq = true)
|
||||
found = []
|
||||
|
||||
target.in_scope_uris(target.homepage_res) do |uri|
|
||||
target.in_scope_uris(page_res) do |uri|
|
||||
next unless uri.to_s =~ item_attribute_pattern(type)
|
||||
|
||||
found << Regexp.last_match[1]
|
||||
slug = Regexp.last_match[1]&.strip
|
||||
|
||||
found << slug unless slug&.empty?
|
||||
end
|
||||
|
||||
uniq ? found.uniq.sort : found.sort
|
||||
@@ -28,7 +30,7 @@ module WPScan
|
||||
def items_from_codes(type, uniq = true)
|
||||
found = []
|
||||
|
||||
target.homepage_res.html.css('script,style').each do |tag|
|
||||
page_res.html.xpath('//script[not(@src)]|//style[not(@src)]').each do |tag|
|
||||
code = tag.text.to_s
|
||||
next if code.empty?
|
||||
|
||||
@@ -42,7 +44,7 @@ module WPScan
|
||||
#
|
||||
# @return [ Regexp ]
|
||||
def item_attribute_pattern(type)
|
||||
@item_attribute_pattern ||= %r{\A#{item_url_pattern(type)}([^/]+)/}i
|
||||
@item_attribute_pattern ||= %r{#{item_url_pattern(type)}([^/]+)/}i
|
||||
end
|
||||
|
||||
# @param [ String ] type
|
||||
@@ -59,7 +61,7 @@ module WPScan
|
||||
item_dir = type == 'plugins' ? target.plugins_dir : target.content_dir
|
||||
item_url = type == 'plugins' ? target.plugins_url : target.content_url
|
||||
|
||||
url = /#{item_url.gsub(/\A(?:http|https)/i, 'https?').gsub('/', '\\\\\?\/')}/i
|
||||
url = /#{item_url.gsub(/\A(?:https?)/i, 'https?').gsub('/', '\\\\\?\/')}/i
|
||||
item_dir = %r{(?:#{url}|\\?\/#{item_dir.gsub('/', '\\\\\?\/')}\\?/)}i
|
||||
|
||||
type == 'plugins' ? item_dir : %r{#{item_dir}#{type}\\?\/}i
|
||||
@@ -15,9 +15,16 @@ module WPScan
|
||||
@uri = Addressable::URI.parse(blog.url(path_from_blog))
|
||||
end
|
||||
|
||||
# @return [ JSON ]
|
||||
# Retrieve the metadata from the vuln API if available (and a valid token is given),
|
||||
# or the local metadata db otherwise
|
||||
# @return [ Hash ]
|
||||
def metadata
|
||||
@metadata ||= db_data.empty? ? DB::Plugin.metadata_at(slug) : db_data
|
||||
end
|
||||
|
||||
# @return [ Hash ]
|
||||
def db_data
|
||||
@db_data ||= DB::Plugin.db_data(slug)
|
||||
@db_data ||= DB::VulnApi.plugin_data(slug)
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
|
||||
@@ -21,9 +21,16 @@ module WPScan
|
||||
parse_style
|
||||
end
|
||||
|
||||
# Retrieve the metadata from the vuln API if available (and a valid token is given),
|
||||
# or the local metadata db otherwise
|
||||
# @return [ JSON ]
|
||||
def metadata
|
||||
@metadata ||= db_data.empty? ? DB::Theme.metadata_at(slug) : db_data
|
||||
end
|
||||
|
||||
# @return [ Hash ]
|
||||
def db_data
|
||||
@db_data ||= DB::Theme.db_data(slug)
|
||||
@db_data ||= DB::VulnApi.theme_data(slug)
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
@@ -94,7 +101,7 @@ module WPScan
|
||||
#
|
||||
# @return [ String ]
|
||||
def parse_style_tag(body, tag)
|
||||
value = body[/^\s*#{Regexp.escape(tag)}:[\t ]*([^\r\n]+)/i, 1]
|
||||
value = body[/#{Regexp.escape(tag)}:[\t ]*([^\r\n\*]+)/i, 1]
|
||||
|
||||
value && !value.strip.empty? ? value.strip : nil
|
||||
end
|
||||
|
||||
@@ -14,7 +14,7 @@ module WPScan
|
||||
|
||||
attr_reader :uri, :slug, :detection_opts, :version_detection_opts, :blog, :path_from_blog, :db_data
|
||||
|
||||
delegate :homepage_res, :xpath_pattern_from_page, :in_scope_uris, :head_or_get_params, to: :blog
|
||||
delegate :homepage_res, :error_404_res, :xpath_pattern_from_page, :in_scope_uris, :head_or_get_params, to: :blog
|
||||
|
||||
# @param [ String ] slug The plugin/theme slug
|
||||
# @param [ Target ] blog The targeted blog
|
||||
@@ -60,18 +60,18 @@ module WPScan
|
||||
|
||||
# @return [ String ]
|
||||
def latest_version
|
||||
@latest_version ||= db_data['latest_version'] ? Model::Version.new(db_data['latest_version']) : nil
|
||||
@latest_version ||= metadata['latest_version'] ? Model::Version.new(metadata['latest_version']) : nil
|
||||
end
|
||||
|
||||
# Not used anywhere ATM
|
||||
# @return [ Boolean ]
|
||||
def popular?
|
||||
@popular ||= db_data['popular']
|
||||
@popular ||= metadata['popular'] ? true : false
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def last_updated
|
||||
@last_updated ||= db_data['last_updated']
|
||||
@last_updated ||= metadata['last_updated']
|
||||
end
|
||||
|
||||
# @return [ Boolean ]
|
||||
|
||||
@@ -35,9 +35,16 @@ module WPScan
|
||||
@all_numbers.sort! { |a, b| Gem::Version.new(b) <=> Gem::Version.new(a) }
|
||||
end
|
||||
|
||||
# @return [ JSON ]
|
||||
# Retrieve the metadata from the vuln API if available (and a valid token is given),
|
||||
# or the local metadata db otherwise
|
||||
# @return [ Hash ]
|
||||
def metadata
|
||||
@metadata ||= db_data.empty? ? DB::Version.metadata_at(number) : db_data
|
||||
end
|
||||
|
||||
# @return [ Hash ]
|
||||
def db_data
|
||||
@db_data ||= DB::Version.db_data(number)
|
||||
@db_data ||= DB::VulnApi.wordpress_data(number)
|
||||
end
|
||||
|
||||
# @return [ Array<Vulnerability> ]
|
||||
@@ -55,12 +62,12 @@ module WPScan
|
||||
|
||||
# @return [ String ]
|
||||
def release_date
|
||||
@release_date ||= db_data['release_date'] || 'Unknown'
|
||||
@release_date ||= metadata['release_date'] || 'Unknown'
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def status
|
||||
@status ||= db_data['status'] || 'Unknown'
|
||||
@status ||= metadata['status'] || 'Unknown'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
_______________________________________________________________
|
||||
__ _______ _____
|
||||
\ \ / / __ \ / ____|
|
||||
\ \ /\ / /| |__) | (___ ___ __ _ _ __ ®
|
||||
\ \/ \/ / | ___/ \___ \ / __|/ _` | '_ \
|
||||
\ /\ / | | ____) | (__| (_| | | | |
|
||||
\/ \/ |_| |_____/ \___|\__,_|_| |_|
|
||||
__ _______ _____
|
||||
\ \ / / __ \ / ____|
|
||||
\ \ /\ / /| |__) | (___ ___ __ _ _ __ ®
|
||||
\ \/ \/ / | ___/ \___ \ / __|/ _` | '_ \
|
||||
\ /\ / | | ____) | (__| (_| | | | |
|
||||
\/ \/ |_| |_____/ \___|\__,_|_| |_|
|
||||
|
||||
WordPress Security Scanner by the WPScan Team
|
||||
Version <%= WPScan::VERSION %>
|
||||
Sponsored by Sucuri - https://sucuri.net
|
||||
@_WPScan_, @ethicalhack3r, @erwan_lr, @_FireFart_
|
||||
WordPress Security Scanner by the WPScan Team
|
||||
Version <%= WPScan::VERSION %>
|
||||
<%= ' ' * ((63 - WPScan::DB::Sponsor.text.length)/2) + WPScan::DB::Sponsor.text %>
|
||||
@_WPScan_, @ethicalhack3r, @erwan_lr, @_FireFart_
|
||||
_______________________________________________________________
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
| Detected By: <%= @item.found_by %>
|
||||
| Found By: <%= @item.found_by %>
|
||||
<% @item.interesting_entries.each do |entry| -%>
|
||||
| - <%= entry %>
|
||||
<% end -%>
|
||||
|
||||
13
app/views/cli/vuln_api/status.erb
Normal file
13
app/views/cli/vuln_api/status.erb
Normal file
@@ -0,0 +1,13 @@
|
||||
<% unless @status.empty? -%>
|
||||
<% if @status['http_error'] -%>
|
||||
<%= critical_icon %> WPVulnDB API, <%= @status['http_error'].to_s %>
|
||||
<% else -%>
|
||||
<%= info_icon %> WPVulnDB API OK
|
||||
| Plan: <%= @status['plan'] %>
|
||||
| Requests Done (during the scan): <%= @api_requests %>
|
||||
| Requests Remaining: <%= @status['requests_remaining'] %>
|
||||
<% end -%>
|
||||
<% else -%>
|
||||
<%= warning_icon %> No WPVulnDB API Token given, as a result vulnerability data has not been output.
|
||||
<%= warning_icon %> You can get a free API token with 50 daily requests by registering at https://wpvulndb.com/users/sign_up.
|
||||
<% end -%>
|
||||
@@ -7,5 +7,5 @@
|
||||
"@erwan_lr",
|
||||
"@_FireFart_"
|
||||
],
|
||||
"sponsored_by": "Sucuri - https://sucuri.net"
|
||||
"sponsor": <%= WPScan::DB::Sponsor.text.to_json %>
|
||||
},
|
||||
|
||||
13
app/views/json/vuln_api/status.erb
Normal file
13
app/views/json/vuln_api/status.erb
Normal file
@@ -0,0 +1,13 @@
|
||||
"vuln_api": {
|
||||
<% unless @status.empty? -%>
|
||||
<% if @status['http_error'] -%>
|
||||
"http_error": <%= @status['http_error'].to_s.to_json %>
|
||||
<% else -%>
|
||||
"plan": <%= @status['plan'].to_json %>,
|
||||
"requests_done_during_scan": <%= @api_requests.to_json %>,
|
||||
"requests_remaining": <%= @status['requests_remaining'].to_json %>
|
||||
<% end -%>
|
||||
<% else -%>
|
||||
"error": "No WPVulnDB API Token given, as a result vulnerability data has not been output.\nYou can get a free API token with 50 daily requests by registering at https://wpvulndb.com/users/sign_up."
|
||||
<% end -%>
|
||||
},
|
||||
@@ -5,6 +5,7 @@ require 'wpscan'
|
||||
|
||||
WPScan::Scan.new do |s|
|
||||
s.controllers <<
|
||||
WPScan::Controller::VulnApi.new <<
|
||||
WPScan::Controller::CustomDirectories.new <<
|
||||
WPScan::Controller::InterestingFindings.new <<
|
||||
WPScan::Controller::WpVersion.new <<
|
||||
|
||||
@@ -7,6 +7,7 @@ require 'wpscan'
|
||||
report = MemoryProfiler.report(top: 15) do
|
||||
WPScan::Scan.new do |s|
|
||||
s.controllers <<
|
||||
WPScan::Controller::VulnApi.new <<
|
||||
WPScan::Controller::CustomDirectories.new <<
|
||||
WPScan::Controller::InterestingFindings.new <<
|
||||
WPScan::Controller::WpVersion.new <<
|
||||
|
||||
@@ -12,6 +12,7 @@ StackProf.run(mode: :cpu, out: '/tmp/stackprof-cpu.dump', interval: 500) do
|
||||
# require_relative 'wpscan' doesn't work
|
||||
WPScan::Scan.new do |s|
|
||||
s.controllers <<
|
||||
WPScan::Controller::VulnApi.new <<
|
||||
WPScan::Controller::CustomDirectories.new <<
|
||||
WPScan::Controller::InterestingFindings.new <<
|
||||
WPScan::Controller::WpVersion.new <<
|
||||
|
||||
@@ -13,7 +13,8 @@ require 'uri'
|
||||
require 'time'
|
||||
require 'readline'
|
||||
require 'securerandom'
|
||||
|
||||
# Monkey Patches/Fixes/Override
|
||||
require 'wpscan/typhoeus/response' # Adds a from_vuln_api? method
|
||||
# Custom Libs
|
||||
require 'wpscan/helper'
|
||||
require 'wpscan/db'
|
||||
@@ -38,12 +39,28 @@ module WPScan
|
||||
APP_DIR = Pathname.new(__FILE__).dirname.join('..', 'app').expand_path
|
||||
DB_DIR = Pathname.new(Dir.home).join('.wpscan', 'db')
|
||||
|
||||
Typhoeus.on_complete do |response|
|
||||
next if response.cached? || !response.from_vuln_api?
|
||||
|
||||
self.api_requests += 1
|
||||
end
|
||||
|
||||
# Override, otherwise it would be returned as 'wp_scan'
|
||||
#
|
||||
# @return [ String ]
|
||||
def self.app_name
|
||||
'wpscan'
|
||||
end
|
||||
|
||||
# @return [ Integer ]
|
||||
def self.api_requests
|
||||
@@api_requests ||= 0
|
||||
end
|
||||
|
||||
# @param [ Integer ] value
|
||||
def self.api_requests=(value)
|
||||
@@api_requests = value
|
||||
end
|
||||
end
|
||||
|
||||
require "#{WPScan::APP_DIR}/app"
|
||||
|
||||
@@ -7,7 +7,7 @@ module WPScan
|
||||
|
||||
# @return [ String ]
|
||||
def default_user_agent
|
||||
"WPScan v#{VERSION} (https://wpscan.org/)"
|
||||
@default_user_agent ||= "WPScan v#{VERSION} (https://wpscan.org/)"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,9 +7,12 @@ require_relative 'db/plugins'
|
||||
require_relative 'db/themes'
|
||||
require_relative 'db/plugin'
|
||||
require_relative 'db/theme'
|
||||
require_relative 'db/sponsor'
|
||||
require_relative 'db/wp_version'
|
||||
require_relative 'db/fingerprints'
|
||||
|
||||
require_relative 'db/vuln_api'
|
||||
|
||||
require_relative 'db/dynamic_finders/base'
|
||||
require_relative 'db/dynamic_finders/plugin'
|
||||
require_relative 'db/dynamic_finders/theme'
|
||||
|
||||
@@ -4,9 +4,9 @@ module WPScan
|
||||
module DB
|
||||
# Plugin DB
|
||||
class Plugin < WpItem
|
||||
# @return [ String ]
|
||||
def self.db_file
|
||||
@db_file ||= DB_DIR.join('plugins.json').to_s
|
||||
# @return [ Hash ]
|
||||
def self.metadata
|
||||
@metadata ||= super['plugins'] || {}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,8 +5,8 @@ module WPScan
|
||||
# WP Plugins
|
||||
class Plugins < WpItems
|
||||
# @return [ JSON ]
|
||||
def self.db
|
||||
Plugin.db
|
||||
def self.metadata
|
||||
Plugin.metadata
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
16
lib/wpscan/db/sponsor.rb
Normal file
16
lib/wpscan/db/sponsor.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
# 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
|
||||
# Theme DB
|
||||
class Theme < WpItem
|
||||
# @return [ String ]
|
||||
def self.db_file
|
||||
@db_file ||= DB_DIR.join('themes.json').to_s
|
||||
# @return [ Hash ]
|
||||
def self.metadata
|
||||
@metadata ||= super['themes'] || {}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,8 +5,8 @@ module WPScan
|
||||
# WP Themes
|
||||
class Themes < WpItems
|
||||
# @return [ JSON ]
|
||||
def self.db
|
||||
Theme.db
|
||||
def self.metadata
|
||||
Theme.metadata
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,12 +7,15 @@ module WPScan
|
||||
class Updater
|
||||
# /!\ Might want to also update the Enumeration#cli_options when some filenames are changed here
|
||||
FILES = %w[
|
||||
plugins.json themes.json wordpresses.json
|
||||
metadata.json wp_fingerprints.json
|
||||
timthumbs-v3.txt config_backups.txt db_exports.txt
|
||||
dynamic_finders.yml wp_fingerprints.json LICENSE
|
||||
dynamic_finders.yml LICENSE sponsor.txt
|
||||
].freeze
|
||||
|
||||
OLD_FILES = %w[wordpress.db user-agents.txt dynamic_finders_01.yml].freeze
|
||||
OLD_FILES = %w[
|
||||
wordpress.db user-agents.txt dynamic_finders_01.yml
|
||||
wordpresses.json plugins.json themes.json
|
||||
].freeze
|
||||
|
||||
attr_reader :repo_directory
|
||||
|
||||
@@ -64,11 +67,12 @@ module WPScan
|
||||
# @return [ Hash ] The params for Typhoeus::Request
|
||||
# @note Those params can't be overriden by CLI options
|
||||
def request_params
|
||||
{
|
||||
@request_params ||= {
|
||||
timeout: 600,
|
||||
connecttimeout: 300,
|
||||
accept_encoding: 'gzip, deflate',
|
||||
cache_ttl: 0
|
||||
cache_ttl: 0,
|
||||
headers: { 'User-Agent' => Browser.instance.default_user_agent, 'Referer' => nil }
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
79
lib/wpscan/db/vuln_api.rb
Normal file
79
lib/wpscan/db/vuln_api.rb
Normal file
@@ -0,0 +1,79 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module DB
|
||||
# WPVulnDB API
|
||||
class VulnApi
|
||||
NON_ERROR_CODES = [200, 401].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 {} if res.code == 404 # This is for API inconsistencies when dots in path
|
||||
return JSON.parse(res.body) if NON_ERROR_CODES.include?(res.code)
|
||||
|
||||
raise Error::HTTP, res
|
||||
rescue Error::HTTP => e
|
||||
retries ||= 0
|
||||
|
||||
if (retries += 1) <= 3
|
||||
sleep(1)
|
||||
retry
|
||||
end
|
||||
|
||||
{ 'http_error' => e }
|
||||
end
|
||||
|
||||
# @return [ Hash ]
|
||||
def self.plugin_data(slug)
|
||||
get("plugins/#{slug}")&.dig(slug) || {}
|
||||
end
|
||||
|
||||
# @return [ Hash ]
|
||||
def self.theme_data(slug)
|
||||
get("themes/#{slug}")&.dig(slug) || {}
|
||||
end
|
||||
|
||||
# @return [ Hash ]
|
||||
def self.wordpress_data(version_number)
|
||||
get("wordpresses/#{version_number.tr('.', '')}")&.dig(version_number) || {}
|
||||
end
|
||||
|
||||
# @return [ Hash ]
|
||||
def self.status
|
||||
json = get('status', params: { version: WPScan::VERSION }, cache_ttl: 0)
|
||||
|
||||
json['requests_remaining'] = 'Unlimited' if json['requests_remaining'] == -1
|
||||
|
||||
json
|
||||
end
|
||||
|
||||
# @return [ Hash ]
|
||||
def self.request_params
|
||||
{
|
||||
headers: {
|
||||
'Host' => uri.host, # Reset in case user provided a --vhost for the target
|
||||
'Referer' => nil, # Removes referer set by the cmsscanner to the target url
|
||||
'User-Agent' => Browser.instance.default_user_agent,
|
||||
'Authorization' => "Token token=#{token}"
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -6,14 +6,19 @@ module WPScan
|
||||
class WpItem
|
||||
# @param [ String ] identifier The plugin/theme slug or version number
|
||||
#
|
||||
# @return [ Hash ] The JSON data from the DB associated to the identifier
|
||||
def self.db_data(identifier)
|
||||
db[identifier] || {}
|
||||
# @return [ Hash ] The JSON data from the metadata associated to the identifier
|
||||
def self.metadata_at(identifier)
|
||||
metadata[identifier] || {}
|
||||
end
|
||||
|
||||
# @return [ JSON ]
|
||||
def self.db
|
||||
@db ||= read_json_file(db_file)
|
||||
def self.metadata
|
||||
@metadata ||= read_json_file(metadata_file)
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def self.metadata_file
|
||||
@metadata_file ||= DB_DIR.join('metadata.json').to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,17 +6,17 @@ module WPScan
|
||||
class WpItems
|
||||
# @return [ Array<String> ] The slug of all items
|
||||
def self.all_slugs
|
||||
db.keys
|
||||
metadata.keys
|
||||
end
|
||||
|
||||
# @return [ Array<String> ] The slug of all popular items
|
||||
def self.popular_slugs
|
||||
db.select { |_key, item| item['popular'] == true }.keys
|
||||
metadata.select { |_key, item| item['popular'] == true }.keys
|
||||
end
|
||||
|
||||
# @return [ Array<String> ] The slug of all vulnerable items
|
||||
def self.vulnerable_slugs
|
||||
db.reject { |_key, item| item['vulnerabilities'].empty? }.keys
|
||||
metadata.select { |_key, item| item['vulnerabilities'] == true }.keys
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,9 +4,9 @@ module WPScan
|
||||
module DB
|
||||
# WP Version
|
||||
class Version < WpItem
|
||||
# @return [ String ]
|
||||
def self.db_file
|
||||
@db_file ||= DB_DIR.join('wordpresses.json').to_s
|
||||
# @return [ Hash ]
|
||||
def self.metadata
|
||||
@metadata ||= super['wordpress'] || {}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,5 +12,6 @@ end
|
||||
require_relative 'errors/enumeration'
|
||||
require_relative 'errors/http'
|
||||
require_relative 'errors/update'
|
||||
require_relative 'errors/vuln_api'
|
||||
require_relative 'errors/wordpress'
|
||||
require_relative 'errors/xmlrpc'
|
||||
|
||||
20
lib/wpscan/errors/vuln_api.rb
Normal file
20
lib/wpscan/errors/vuln_api.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Error
|
||||
# Error raised when the token given via --api-token is invalid
|
||||
class InvalidApiToken < Standard
|
||||
def to_s
|
||||
'The API token provided is invalid'
|
||||
end
|
||||
end
|
||||
|
||||
# Error raised when the number of API requests has been reached
|
||||
# currently not implemented on the API side
|
||||
class ApiLimitReached < Standard
|
||||
def to_s
|
||||
'Your API limit has been reached'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -44,19 +44,27 @@ module WPScan
|
||||
#
|
||||
# @param [ Typhoeus::Response ] response
|
||||
# @param [ Hash ] opts
|
||||
# @return [ Mixed ]
|
||||
# @return [ Mixed: nil, Object, Array ]
|
||||
def find(_response, _opts = {})
|
||||
raise NoMethodError
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
# @return [ Mixed ] See #find
|
||||
def passive(opts = {})
|
||||
return if self.class::PATH
|
||||
|
||||
find(target.homepage_res, opts)
|
||||
homepage_result = find(target.homepage_res, opts)
|
||||
|
||||
if homepage_result
|
||||
return homepage_result unless homepage_result.is_a?(Array) && homepage_result.empty?
|
||||
end
|
||||
|
||||
find(target.error_404_res, opts)
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
# @return [ Mixed ] See #find
|
||||
def aggressive(opts = {})
|
||||
return unless self.class::PATH
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ module WPScan
|
||||
module Finders
|
||||
module DynamicFinder
|
||||
module Version
|
||||
# Version finder using Body Pattern method. Tipically used when the response is not
|
||||
# Version finder using Body Pattern method. Typically used when the response is not
|
||||
# an HTML doc and Xpath can't be used
|
||||
class BodyPattern < Finders::DynamicFinder::Version::Finder
|
||||
# @return [ Hash ]
|
||||
@@ -16,7 +16,7 @@ module WPScan
|
||||
# @param [ Hash ] opts
|
||||
# @return [ Version ]
|
||||
def find(response, _opts = {})
|
||||
return unless response.body =~ self.class::PATTERN
|
||||
return unless response.code != 404 && response.body =~ self.class::PATTERN
|
||||
|
||||
create_version(
|
||||
Regexp.last_match[:v],
|
||||
|
||||
@@ -31,9 +31,14 @@ module WPScan
|
||||
|
||||
passive_configs.each do |slug, configs|
|
||||
configs.each do |klass, config|
|
||||
item = process_response(opts, target.homepage_res, slug, klass, config)
|
||||
[target.homepage_res, target.error_404_res].each do |page_res|
|
||||
item = process_response(opts, page_res, slug, klass, config)
|
||||
|
||||
found << item if item.is_a?(Model::WpItem)
|
||||
if item.is_a?(Model::WpItem)
|
||||
found << item
|
||||
break # No need to check the other page if detected in the current
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -6,13 +6,15 @@ rescue StandardError => e
|
||||
raise "JSON parsing error in #{file} #{e}"
|
||||
end
|
||||
|
||||
# @return [ Symbol ]
|
||||
# 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
|
||||
# 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 ]
|
||||
def classify_slug(slug)
|
||||
classified = slug.to_s.tr('-', '_').camelize.to_s
|
||||
classified = "D_#{classified}" if classified[0] =~ /\d/
|
||||
classified = slug.to_s.gsub(/[^a-z\d\-]/i, '-').gsub(/\-{1,}/, '_').camelize.to_s
|
||||
classified = "D_#{classified}" if /\d/.match?(classified[0])
|
||||
|
||||
classified.to_sym
|
||||
end
|
||||
|
||||
@@ -11,7 +11,9 @@ module WPScan
|
||||
module WordPress
|
||||
include CMSScanner::Target::Platform::PHP
|
||||
|
||||
WORDPRESS_PATTERN = %r{/(?:(?:wp-content/(?:themes|(?:mu\-)?plugins|uploads))|wp-includes)/}i.freeze
|
||||
WORDPRESS_PATTERN = %r{/(?:(?:wp-content/(?:themes|(?:mu\-)?plugins|uploads))|wp-includes)/}i.freeze
|
||||
WP_JSON_OEMBED_PATTERN = %r{/wp\-json/oembed/}i.freeze
|
||||
WP_ADMIN_AJAX_PATTERN = %r{\\?/wp\-admin\\?/admin\-ajax\.php}i.freeze
|
||||
|
||||
# These methods are used in the associated interesting_findings finders
|
||||
# to keep the boolean state of the finding rather than re-check the whole thing again
|
||||
@@ -22,22 +24,16 @@ module WPScan
|
||||
|
||||
# @param [ Symbol ] detection_mode
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
# @return [ Boolean ] Whether or not the target is running WordPress
|
||||
def wordpress?(detection_mode)
|
||||
in_scope_uris(homepage_res) do |uri|
|
||||
return true if uri.path.match(WORDPRESS_PATTERN)
|
||||
[homepage_res, error_404_res].each do |page_res|
|
||||
return true if wordpress_from_meta_comments_or_scripts?(page_res)
|
||||
end
|
||||
|
||||
homepage_res.html.css('meta[name="generator"]').each do |node|
|
||||
return true if node['content'] =~ /wordpress/i
|
||||
end
|
||||
|
||||
return true unless comments_from_page(/wordpress/i, homepage_res).empty?
|
||||
|
||||
if %i[mixed aggressive].include?(detection_mode)
|
||||
%w[wp-admin/install.php wp-login.php].each do |path|
|
||||
in_scope_uris(Browser.get_and_follow_location(url(path))).each do |uri|
|
||||
return true if uri.path.match(WORDPRESS_PATTERN)
|
||||
return true if in_scope_uris(Browser.get_and_follow_location(url(path))).any? do |uri|
|
||||
WORDPRESS_PATTERN.match?(uri.path)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -45,6 +41,26 @@ module WPScan
|
||||
false
|
||||
end
|
||||
|
||||
# @param [ Typhoeus::Response ] response
|
||||
# @return [ Boolean ]
|
||||
def wordpress_from_meta_comments_or_scripts?(response)
|
||||
in_scope_uris(response) do |uri|
|
||||
return true if WORDPRESS_PATTERN.match?(uri.path) || WP_JSON_OEMBED_PATTERN.match?(uri.path)
|
||||
end
|
||||
|
||||
return true if response.html.css('meta[name="generator"]').any? do |node|
|
||||
/wordpress/i.match?(node['content'])
|
||||
end
|
||||
|
||||
return true unless comments_from_page(/wordpress/i, response).empty?
|
||||
|
||||
return true if response.html.xpath('//script[not(@src)]').any? do |node|
|
||||
WP_ADMIN_AJAX_PATTERN.match?(node.text)
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
COOKIE_PATTERNS = {
|
||||
'vjs' => /createCookie\('vjs','(?<c_value>\d+)',\d+\);/i
|
||||
}.freeze
|
||||
@@ -82,7 +98,7 @@ module WPScan
|
||||
def wordpress_hosted?
|
||||
return true if /\.wordpress\.com$/i.match?(uri.host)
|
||||
|
||||
unless content_dir(:passive)
|
||||
unless content_dir
|
||||
pattern = %r{https?://s\d\.wp\.com#{WORDPRESS_PATTERN}}i.freeze
|
||||
|
||||
uris_from_page(homepage_res) do |uri|
|
||||
@@ -109,6 +125,7 @@ module WPScan
|
||||
Browser.instance.forge_request(
|
||||
login_url,
|
||||
method: :post,
|
||||
cache_ttl: 0,
|
||||
body: { log: username, pwd: password }
|
||||
)
|
||||
end
|
||||
|
||||
@@ -13,25 +13,24 @@ module WPScan
|
||||
@plugins_dir = dir.chomp('/')
|
||||
end
|
||||
|
||||
# @param [ Symbol ] detection_mode
|
||||
# @return [ String ] The wp-content directory
|
||||
def content_dir(detection_mode = :mixed)
|
||||
def content_dir
|
||||
unless @content_dir
|
||||
# scope_url_pattern is from CMSScanner::Target
|
||||
pattern = %r{#{scope_url_pattern}([\w\s\-/]+)\\?/(?:themes|plugins|uploads|cache)\\?/}i
|
||||
pattern = %r{#{scope_url_pattern}([\w\s\-/]+?)\\?/(?:themes|plugins|uploads|cache)\\?/}i
|
||||
|
||||
in_scope_uris(homepage_res) do |uri|
|
||||
return @content_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
|
||||
[homepage_res, error_404_res].each do |page_res|
|
||||
in_scope_uris(page_res, '//link/@href|//script/@src|//img/@src') do |uri|
|
||||
return @content_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
|
||||
end
|
||||
|
||||
# Checks for the pattern in raw JS code, as well as @content attributes of meta tags
|
||||
xpath_pattern_from_page('//script[not(@src)]|//meta/@content', pattern, page_res) do |match|
|
||||
return @content_dir = match[1]
|
||||
end
|
||||
end
|
||||
|
||||
# Checks for the pattern in raw JS code, as well as @content attributes of meta tags
|
||||
xpath_pattern_from_page('//script[not(@src)]|//meta/@content', pattern, homepage_res) do |match|
|
||||
return @content_dir = match[1]
|
||||
end
|
||||
|
||||
unless detection_mode == :passive
|
||||
return @content_dir = 'wp-content' if default_content_dir_exists?
|
||||
end
|
||||
return @content_dir = 'wp-content' if default_content_dir_exists?
|
||||
end
|
||||
|
||||
@content_dir
|
||||
@@ -107,8 +106,10 @@ module WPScan
|
||||
# url_pattern is from CMSScanner::Target
|
||||
pattern = %r{#{url_pattern}(.+?)/(?:xmlrpc\.php|wp\-includes/)}i
|
||||
|
||||
in_scope_uris(homepage_res) do |uri|
|
||||
return @sub_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
|
||||
[homepage_res, error_404_res].each do |page_res|
|
||||
in_scope_uris(page_res) do |uri|
|
||||
return @sub_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
|
||||
end
|
||||
end
|
||||
|
||||
@sub_dir = false
|
||||
|
||||
13
lib/wpscan/typhoeus/response.rb
Normal file
13
lib/wpscan/typhoeus/response.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Typhoeus
|
||||
# Custom Response class
|
||||
class Response
|
||||
# @note: Ignores requests done to the /status endpoint of the API
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def from_vuln_api?
|
||||
effective_url.start_with?(WPScan::DB::VulnApi.uri.to_s) && !effective_url.include?('/status')
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -2,5 +2,5 @@
|
||||
|
||||
# Version
|
||||
module WPScan
|
||||
VERSION = '3.6.0'
|
||||
VERSION = '3.7.5'
|
||||
end
|
||||
|
||||
@@ -166,6 +166,7 @@ describe WPScan::Controller::Core do
|
||||
before do
|
||||
expect(core).to receive(:load_server_module)
|
||||
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true)
|
||||
expect(core.target).to receive(:wordpress_hosted?).and_return(false)
|
||||
end
|
||||
|
||||
it 'calls the formatter when started and finished to update the db' do
|
||||
@@ -174,56 +175,6 @@ describe WPScan::Controller::Core do
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a redirect occurs' do
|
||||
before do
|
||||
stub_request(:any, target_url)
|
||||
|
||||
expect(core.target).to receive(:homepage_res)
|
||||
.at_least(1)
|
||||
.and_return(Typhoeus::Response.new(effective_url: redirection, body: ''))
|
||||
end
|
||||
|
||||
context 'to the wp-admin/install.php' do
|
||||
let(:redirection) { "#{target_url}wp-admin/install.php" }
|
||||
|
||||
it 'calls the formatter with the correct parameters and exit' do
|
||||
expect(core.formatter).to receive(:output)
|
||||
.with('not_fully_configured', hash_including(url: redirection), 'core').ordered
|
||||
|
||||
# TODO: Would be cool to be able to test the exit code
|
||||
expect { core.before_scan }.to raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
|
||||
context 'to something else' do
|
||||
let(:redirection) { 'http://g.com/' }
|
||||
|
||||
it 'raises an error' do
|
||||
expect { core.before_scan }.to raise_error(CMSScanner::Error::HTTPRedirect)
|
||||
end
|
||||
end
|
||||
|
||||
context 'to another path with the wp-admin/install.php in the query' do
|
||||
let(:redirection) { "#{target_url}index.php?a=/wp-admin/install.php" }
|
||||
|
||||
context 'when wordpress' do
|
||||
it 'does not raise an error' do
|
||||
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true)
|
||||
|
||||
expect { core.before_scan }.to_not raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not wordpress' do
|
||||
it 'raises an error' do
|
||||
expect(core.target).to receive(:wordpress?).twice.with(:mixed).and_return(false)
|
||||
|
||||
expect { core.before_scan }.to raise_error(WPScan::Error::NotWordPress)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when hosted on wordpress.com' do
|
||||
let(:target_url) { 'http://ex.wordpress.com' }
|
||||
|
||||
@@ -234,52 +185,106 @@ describe WPScan::Controller::Core do
|
||||
end
|
||||
end
|
||||
|
||||
context 'when wordpress' do
|
||||
before do
|
||||
expect(core).to receive(:load_server_module)
|
||||
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true)
|
||||
end
|
||||
context 'when not hosted on wordpress.com' do
|
||||
before { allow(core.target).to receive(:wordpress_hosted?).and_return(false) }
|
||||
|
||||
it 'does not raise any error' do
|
||||
expect { core.before_scan }.to_not raise_error
|
||||
end
|
||||
end
|
||||
context 'when a redirect occurs' do
|
||||
before do
|
||||
stub_request(:any, target_url)
|
||||
|
||||
context 'when not wordpress' do
|
||||
before do
|
||||
expect(core).to receive(:load_server_module)
|
||||
end
|
||||
expect(core.target).to receive(:homepage_res)
|
||||
.at_least(1)
|
||||
.and_return(Typhoeus::Response.new(effective_url: redirection, body: ''))
|
||||
end
|
||||
|
||||
context 'when no --force' do
|
||||
before { expect(core.target).to receive(:maybe_add_cookies) }
|
||||
context 'to the wp-admin/install.php' do
|
||||
let(:redirection) { "#{target_url}wp-admin/install.php" }
|
||||
|
||||
context 'when no cookies added or still not wordpress after being added' do
|
||||
it 'raises an error' do
|
||||
expect(core.target).to receive(:wordpress?).twice.with(:mixed).and_return(false)
|
||||
it 'calls the formatter with the correct parameters and exit' do
|
||||
expect(core.formatter).to receive(:output)
|
||||
.with('not_fully_configured', hash_including(url: redirection), 'core').ordered
|
||||
|
||||
expect { core.before_scan }.to raise_error(WPScan::Error::NotWordPress)
|
||||
# TODO: Would be cool to be able to test the exit code
|
||||
expect { core.before_scan }.to raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the added cookies solved it' do
|
||||
it 'does not raise an error' do
|
||||
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(false).ordered
|
||||
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true).ordered
|
||||
context 'to something else' do
|
||||
let(:redirection) { 'http://g.com/' }
|
||||
|
||||
it 'raises an error' do
|
||||
expect { core.before_scan }.to raise_error(CMSScanner::Error::HTTPRedirect)
|
||||
end
|
||||
end
|
||||
|
||||
context 'to another path with the wp-admin/install.php in the query' do
|
||||
let(:redirection) { "#{target_url}index.php?a=/wp-admin/install.php" }
|
||||
|
||||
context 'when wordpress' do
|
||||
it 'does not raise an error' do
|
||||
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true)
|
||||
|
||||
expect { core.before_scan }.to_not raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not wordpress' do
|
||||
it 'raises an error' do
|
||||
expect(core.target).to receive(:wordpress?).twice.with(:mixed).and_return(false)
|
||||
|
||||
expect { core.before_scan }.to raise_error(WPScan::Error::NotWordPress)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when wordpress' do
|
||||
before do
|
||||
expect(core).to receive(:load_server_module)
|
||||
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true)
|
||||
end
|
||||
|
||||
it 'does not raise any error' do
|
||||
expect { core.before_scan }.to_not raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not wordpress' do
|
||||
before do
|
||||
expect(core).to receive(:load_server_module)
|
||||
end
|
||||
|
||||
context 'when no --force' do
|
||||
before { expect(core.target).to receive(:maybe_add_cookies) }
|
||||
|
||||
context 'when no cookies added or still not wordpress after being added' do
|
||||
it 'raises an error' do
|
||||
expect(core.target).to receive(:wordpress?).twice.with(:mixed).and_return(false)
|
||||
|
||||
expect { core.before_scan }.to raise_error(WPScan::Error::NotWordPress)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the added cookies solved it' do
|
||||
it 'does not raise an error' do
|
||||
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(false).ordered
|
||||
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true).ordered
|
||||
|
||||
expect { core.before_scan }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when --force' do
|
||||
let(:cli_args) { "#{super()} --force" }
|
||||
|
||||
it 'does not raise any error' do
|
||||
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(false)
|
||||
|
||||
expect { core.before_scan }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when --force' do
|
||||
let(:cli_args) { "#{super()} --force" }
|
||||
|
||||
it 'does not raise any error' do
|
||||
expect(core.target).to receive(:wordpress?).with(:mixed).and_return(false)
|
||||
|
||||
expect { core.before_scan }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -20,7 +20,7 @@ describe WPScan::Controller::CustomDirectories do
|
||||
|
||||
describe '#before_scan' do
|
||||
context 'when the content_dir is not found and not supplied' do
|
||||
before { expect(controller.target).to receive(:content_dir).with(:mixed) }
|
||||
before { expect(controller.target).to receive(:content_dir).and_return(nil) }
|
||||
|
||||
it 'raises an exception' do
|
||||
expect { controller.before_scan }.to raise_error(WPScan::Error::WpContentDirNotDetected)
|
||||
|
||||
93
spec/app/controllers/vuln_api_spec.rb
Normal file
93
spec/app/controllers/vuln_api_spec.rb
Normal file
@@ -0,0 +1,93 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe WPScan::Controller::VulnApi do
|
||||
subject(:controller) { described_class.new }
|
||||
let(:target_url) { 'http://ex.lo/' }
|
||||
let(:cli_args) { "--url #{target_url}" }
|
||||
|
||||
before do
|
||||
WPScan::ParsedCli.options = rspec_parsed_options(cli_args)
|
||||
end
|
||||
|
||||
describe '#cli_options' do
|
||||
its(:cli_options) { should_not be_empty }
|
||||
its(:cli_options) { should be_a Array }
|
||||
|
||||
it 'contains to correct options' do
|
||||
expect(controller.cli_options.map(&:to_sym)).to eq %i[api_token]
|
||||
end
|
||||
end
|
||||
|
||||
describe '#before_scan' do
|
||||
context 'when no --api-token provided' do
|
||||
its(:before_scan) { should be nil }
|
||||
end
|
||||
|
||||
context 'when --api-token given' do
|
||||
let(:cli_args) { "#{super()} --api-token token" }
|
||||
|
||||
context 'when the token is invalid' do
|
||||
before { expect(WPScan::DB::VulnApi).to receive(:status).and_return('error' => 'HTTP Token: Access denied.') }
|
||||
|
||||
it 'raise an InvalidApiToken error' do
|
||||
expect { controller.before_scan }.to raise_error(WPScan::Error::InvalidApiToken)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the token is valid' do
|
||||
context 'when the limit has been reached' do
|
||||
before do
|
||||
expect(WPScan::DB::VulnApi)
|
||||
.to receive(:status)
|
||||
.and_return('success' => true, 'plan' => 'free', 'requests_remaining' => 0)
|
||||
end
|
||||
|
||||
it 'raises an ApiLimitReached error' do
|
||||
expect { controller.before_scan }.to raise_error(WPScan::Error::ApiLimitReached)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a HTTP error, like a timeout' do
|
||||
before do
|
||||
expect(WPScan::DB::VulnApi)
|
||||
.to receive(:status)
|
||||
.and_return(
|
||||
'http_error' => WPScan::Error::HTTP.new(
|
||||
Typhoeus::Response.new(effective_url: 'mock-url', return_code: 28)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
it 'raises an HTTP error' do
|
||||
expect { controller.before_scan }
|
||||
.to raise_error(WPScan::Error::HTTP, 'HTTP Error: mock-url (Timeout was reached)')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the token is valid and no HTTP error' do
|
||||
before do
|
||||
expect(WPScan::DB::VulnApi)
|
||||
.to receive(:status)
|
||||
.and_return('success' => true, 'plan' => 'free', 'requests_remaining' => requests)
|
||||
end
|
||||
|
||||
context 'when limited requests' do
|
||||
let(:requests) { 100 }
|
||||
|
||||
it 'does not raise an error' do
|
||||
expect { controller.before_scan }.to_not raise_error
|
||||
end
|
||||
|
||||
context 'when unlimited requests' do
|
||||
let(:requests) { 'Unlimited' }
|
||||
|
||||
it 'does not raise an error' do
|
||||
expect { controller.before_scan }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -9,7 +9,7 @@ describe WPScan::Finders::DbExports::KnownLocations do
|
||||
|
||||
describe '#potential_urls' do
|
||||
before do
|
||||
expect(target).to receive(:sub_dir).at_least(1).and_return(false)
|
||||
allow(target).to receive(:sub_dir).and_return(false)
|
||||
end
|
||||
|
||||
it 'replace {domain_name} by its value' do
|
||||
@@ -22,11 +22,45 @@ describe WPScan::Finders::DbExports::KnownLocations do
|
||||
http://ex.lo/aa/backups/db_backup.sql
|
||||
]
|
||||
end
|
||||
|
||||
%w[dev poc www].each do |sub_domain|
|
||||
context "when #{sub_domain} sub-domain" do
|
||||
let(:url) { "https://#{sub_domain}.domain.tld" }
|
||||
|
||||
it 'replace {domain_name} by its correct value' do
|
||||
expect(finder.potential_urls(opts).keys).to include "#{url}/domain.sql"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when multi-level tlds' do
|
||||
let(:url) { 'https://something.com.tr' }
|
||||
|
||||
it 'replace {domain_name} by its correct value' do
|
||||
expect(finder.potential_urls(opts).keys).to include 'https://something.com.tr/something.sql'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when multi-level tlds and sub-domain' do
|
||||
let(:url) { 'https://dev.something.com.tr' }
|
||||
|
||||
it 'replace {domain_name} by its correct value' do
|
||||
expect(finder.potential_urls(opts).keys).to include 'https://dev.something.com.tr/something.sql'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when some weird stuff' do
|
||||
let(:url) { 'https://098f6bcd4621d373cade4e832627b4f6.aa-bb-ccc-dd.domain-test.com' }
|
||||
|
||||
it 'replace {domain_name} by its correct value' do
|
||||
expect(finder.potential_urls(opts).keys).to include "#{url}/domain-test.sql"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#aggressive' do
|
||||
before do
|
||||
expect(target).to receive(:sub_dir).at_least(1).and_return(false)
|
||||
allow(target).to receive(:sub_dir).and_return(false)
|
||||
expect(target).to receive(:head_or_get_params).and_return(method: :head)
|
||||
|
||||
finder.potential_urls(opts).each_key do |url|
|
||||
|
||||
11
spec/app/finders/main_theme/css_style_in_404_page_spec.rb
Normal file
11
spec/app/finders/main_theme/css_style_in_404_page_spec.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe WPScan::Finders::MainTheme::CssStyleIn404Page do
|
||||
subject(:finder) { described_class.new(target) }
|
||||
let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) }
|
||||
let(:url) { 'http://wp.lab/' }
|
||||
let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'css_style_in_404_page') }
|
||||
|
||||
# This stuff is just a child class of CssStyleInHomepage (using the error_404_res rather than homepage_res)
|
||||
# which already has a spec
|
||||
end
|
||||
@@ -1,10 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe WPScan::Finders::MainTheme::CssStyle do
|
||||
describe WPScan::Finders::MainTheme::CssStyleInHomepage do
|
||||
subject(:finder) { described_class.new(target) }
|
||||
let(:target) { WPScan::Target.new(url).extend(CMSScanner::Target::Server::Apache) }
|
||||
let(:url) { 'http://wp.lab/' }
|
||||
let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'css_style') }
|
||||
let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'css_style_in_homepage') }
|
||||
|
||||
describe '#passive' do
|
||||
after do
|
||||
@@ -33,7 +33,7 @@ describe WPScan::Finders::MainTheme::CssStyle do
|
||||
@expected = WPScan::Model::Theme.new(
|
||||
'twentyfifteen',
|
||||
target,
|
||||
found_by: 'Css Style (Passive Detection)',
|
||||
found_by: 'Css Style In Homepage (Passive Detection)',
|
||||
confidence: 70,
|
||||
style_url: 'http://wp.lab/wp-content/themes/twentyfifteen/style.css?ver=4.1.1'
|
||||
)
|
||||
@@ -47,7 +47,7 @@ describe WPScan::Finders::MainTheme::CssStyle do
|
||||
@expected = WPScan::Model::Theme.new(
|
||||
'custom',
|
||||
target,
|
||||
found_by: 'Css Style (Passive Detection)',
|
||||
found_by: 'Css Style In Homepage (Passive Detection)',
|
||||
confidence: 70,
|
||||
style_url: 'http://wp.lab/wp-content/themes/custom/style.css'
|
||||
)
|
||||
11
spec/app/finders/main_theme/urls_in_404_page_spec.rb.rb
Normal file
11
spec/app/finders/main_theme/urls_in_404_page_spec.rb.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe WPScan::Finders::MainTheme::UrlsIn404Page do
|
||||
subject(:finder) { described_class.new(target) }
|
||||
let(:target) { WPScan::Target.new(url) }
|
||||
let(:url) { 'http://wp.lab/' }
|
||||
let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'urls_in_404_page') }
|
||||
|
||||
# This stuff is just a child class of URLsInHomepage (using the error_404_res rather than homepage_res)
|
||||
# which already has a spec
|
||||
end
|
||||
@@ -6,7 +6,8 @@ describe WPScan::Finders::MainTheme::UrlsInHomepage do
|
||||
let(:url) { 'http://wp.lab/' }
|
||||
let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'urls_in_homepage') }
|
||||
|
||||
it_behaves_like 'App::Finders::WpItems::URLsInHomepage' do
|
||||
it_behaves_like 'App::Finders::WpItems::UrlsInPage' do
|
||||
let(:page_url) { url }
|
||||
let(:type) { 'themes' }
|
||||
let(:uniq_links) { false }
|
||||
let(:uniq_codes) { false }
|
||||
@@ -18,6 +19,8 @@ describe WPScan::Finders::MainTheme::UrlsInHomepage do
|
||||
before do
|
||||
stub_request(:get, /.*.css/)
|
||||
stub_request(:get, target.url).to_return(body: File.read(fixtures.join('found.html')))
|
||||
|
||||
allow(target).to receive(:content_dir).and_return('wp-content')
|
||||
end
|
||||
|
||||
it 'returns the expected Themes' do
|
||||
|
||||
@@ -7,32 +7,50 @@ describe WPScan::Finders::MainTheme::WooFrameworkMetaGenerator do
|
||||
let(:fixtures) { FINDERS_FIXTURES.join('main_theme', 'woo_framework_meta_generator') }
|
||||
|
||||
describe '#passive' do
|
||||
after do
|
||||
stub_request(:get, url).to_return(body: File.read(fixtures.join(@file)))
|
||||
|
||||
expect(finder.passive).to eql @expected
|
||||
before do
|
||||
stub_request(:get, url).to_return(body: File.read(fixtures.join(homepage_fixture)))
|
||||
stub_request(:get, ERROR_404_URL_PATTERN).to_return(body: File.read(fixtures.join(error_404_fixture)))
|
||||
end
|
||||
|
||||
context 'when no Woo generator' do
|
||||
let(:homepage_fixture) { 'no_woo_generator.html' }
|
||||
let(:error_404_fixture) { 'no_woo_generator.html' }
|
||||
|
||||
it 'returns nil' do
|
||||
@file = 'no_woo_generator.html'
|
||||
@expected = nil
|
||||
expect(finder.passive).to eql nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when Woo generator' do
|
||||
before do
|
||||
expect(target).to receive(:content_dir).at_least(1).and_return('wp-content')
|
||||
allow(target).to receive(:content_dir).and_return('wp-content')
|
||||
stub_request(:get, "#{url}wp-content/themes/Merchant/style.css")
|
||||
end
|
||||
|
||||
it 'returns the expected theme' do
|
||||
@file = 'woo_generator.html'
|
||||
@expected = WPScan::Model::Theme.new(
|
||||
'Merchant', target,
|
||||
found_by: 'Woo Framework Meta Generator (Passive Detection)',
|
||||
confidence: 80
|
||||
)
|
||||
context 'from the homepage' do
|
||||
let(:homepage_fixture) { 'woo_generator.html' }
|
||||
let(:error_404_fixture) { 'no_woo_generator.html' }
|
||||
|
||||
it 'returns the expected theme' do
|
||||
expect(finder.passive).to eql WPScan::Model::Theme.new(
|
||||
'Merchant', target,
|
||||
found_by: 'Woo Framework Meta Generator (Passive Detection)',
|
||||
confidence: 80
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'from the 404 page' do
|
||||
let(:homepage_fixture) { 'no_woo_generator.html' }
|
||||
let(:error_404_fixture) { 'woo_generator.html' }
|
||||
|
||||
it 'returns the expected theme' do
|
||||
expect(finder.passive).to eql WPScan::Model::Theme.new(
|
||||
'Merchant', target,
|
||||
found_by: 'Woo Framework Meta Generator (Passive Detection)',
|
||||
confidence: 80
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,7 +8,7 @@ describe WPScan::Finders::MainTheme::Base do
|
||||
describe '#finders' do
|
||||
it 'contains the expected finders' do
|
||||
expect(main_theme.finders.map { |f| f.class.to_s.demodulize })
|
||||
.to eq %w[CssStyle WooFrameworkMetaGenerator UrlsInHomepage]
|
||||
.to eq %w[CssStyleInHomepage CssStyleIn404Page WooFrameworkMetaGenerator UrlsInHomepage UrlsIn404Page]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
61
spec/app/finders/passwords/wp_login_spec.rb
Normal file
61
spec/app/finders/passwords/wp_login_spec.rb
Normal file
@@ -0,0 +1,61 @@
|
||||
# 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
|
||||
11
spec/app/finders/plugins/urls_in_404_page_spec.rb
Normal file
11
spec/app/finders/plugins/urls_in_404_page_spec.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe WPScan::Finders::Plugins::UrlsIn404Page do
|
||||
subject(:finder) { described_class.new(target) }
|
||||
let(:target) { WPScan::Target.new(url) }
|
||||
let(:url) { 'https://wp.lab/' }
|
||||
let(:fixtures) { FINDERS_FIXTURES.join('plugins', 'urls_in_404_page') }
|
||||
|
||||
# This stuff is just a child class of URLsInHomepage (using the error_404_res rather than homepage_res)
|
||||
# which already has a spec
|
||||
end
|
||||
@@ -3,14 +3,17 @@
|
||||
describe WPScan::Finders::Plugins::UrlsInHomepage do
|
||||
subject(:finder) { described_class.new(target) }
|
||||
let(:target) { WPScan::Target.new(url) }
|
||||
let(:url) { 'http://wp.lab/' }
|
||||
let(:url) { 'https://wp.lab/' }
|
||||
let(:fixtures) { FINDERS_FIXTURES.join('plugins', 'urls_in_homepage') }
|
||||
|
||||
it_behaves_like 'App::Finders::WpItems::URLsInHomepage' do
|
||||
before { target.scope << 'sub.lab' }
|
||||
|
||||
it_behaves_like 'App::Finders::WpItems::UrlsInPage' do
|
||||
let(:page_url) { url }
|
||||
let(:type) { 'plugins' }
|
||||
let(:uniq_links) { true }
|
||||
let(:uniq_codes) { true }
|
||||
let(:expected_from_links) { (1..4).map { |i| "dl-#{i}" } }
|
||||
let(:expected_from_links) { (1..5).map { |i| "dl-#{i}" } }
|
||||
let(:expected_from_codes) { (1..6).map { |i| "dc-#{i}" } }
|
||||
end
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ describe WPScan::Finders::Plugins::Base do
|
||||
describe '#finders' do
|
||||
it 'contains the expected finders' do
|
||||
expect(plugins.finders.map { |f| f.class.to_s.demodulize })
|
||||
.to eq %w[UrlsInHomepage HeaderPattern Comment Xpath BodyPattern JavascriptVar KnownLocations]
|
||||
.to eq %w[UrlsInHomepage UrlsIn404Page HeaderPattern Comment Xpath BodyPattern JavascriptVar KnownLocations]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
11
spec/app/finders/themes/urls_in_404_page_spec.rb
Normal file
11
spec/app/finders/themes/urls_in_404_page_spec.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
describe WPScan::Finders::Themes::UrlsIn404Page do
|
||||
subject(:finder) { described_class.new(target) }
|
||||
let(:target) { WPScan::Target.new(url) }
|
||||
let(:url) { 'http://wp.lab/' }
|
||||
let(:fixtures) { FINDERS_FIXTURES.join('themes', 'urls_in_404_page') }
|
||||
|
||||
# This stuff is just a child class of URLsInHomepage (using the error_404_res rather than homepage_res)
|
||||
# which already has a spec
|
||||
end
|
||||
@@ -6,7 +6,10 @@ describe WPScan::Finders::Themes::UrlsInHomepage do
|
||||
let(:url) { 'http://wp.lab/' }
|
||||
let(:fixtures) { FINDERS_FIXTURES.join('themes', 'urls_in_homepage') }
|
||||
|
||||
it_behaves_like 'App::Finders::WpItems::URLsInHomepage' do
|
||||
# before { target.scope << 'sub.lab' }
|
||||
|
||||
it_behaves_like 'App::Finders::WpItems::UrlsInPage' do
|
||||
let(:page_url) { url }
|
||||
let(:type) { 'themes' }
|
||||
let(:uniq_links) { true }
|
||||
let(:uniq_codes) { true }
|
||||
|
||||
@@ -8,7 +8,7 @@ describe WPScan::Finders::Themes::Base do
|
||||
describe '#finders' do
|
||||
it 'contains the expected finders' do
|
||||
expect(themes.finders.map { |f| f.class.to_s.demodulize })
|
||||
.to eq %w[UrlsInHomepage KnownLocations]
|
||||
.to eq %w[UrlsInHomepage UrlsIn404Page KnownLocations]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -50,7 +50,7 @@ describe WPScan::Finders::Users::AuthorIdBruteForcing do
|
||||
end
|
||||
|
||||
context 'when no display_name' do
|
||||
['4.1.1', '3.0', '2.9.2'].each do |file|
|
||||
%w[4.9-span-tag 4.1.1 3.0 2.9.2].each do |file|
|
||||
it "returns nil for #{file}-empty.html" do
|
||||
body = File.read(fixtures.join("#{file}-empty.html"))
|
||||
|
||||
|
||||
@@ -19,15 +19,17 @@ describe WPScan::Finders::Users::OembedApi do
|
||||
end
|
||||
|
||||
context 'when a JSON response' do
|
||||
let(:body) { File.read(fixture) }
|
||||
|
||||
context 'when 404' do
|
||||
let(:body) { File.read(fixtures.join('404.json')) }
|
||||
let(:fixture) { fixtures.join('404.json') }
|
||||
|
||||
its(:aggressive) { should eql([]) }
|
||||
end
|
||||
|
||||
context 'when 200' do
|
||||
context 'when author_url present' do
|
||||
let(:body) { File.read(fixtures.join('200_author_url.json')) }
|
||||
let(:fixture) { fixtures.join('200_author_url.json') }
|
||||
|
||||
it 'returns the expected array of users' do
|
||||
users = finder.aggressive
|
||||
@@ -44,7 +46,7 @@ describe WPScan::Finders::Users::OembedApi do
|
||||
end
|
||||
|
||||
context 'when author_url not present but author_name' do
|
||||
let(:body) { File.read(fixtures.join('200_author_name.json')) }
|
||||
let(:fixture) { fixtures.join('200_author_name.json') }
|
||||
|
||||
it 'returns the expected array of users' do
|
||||
users = finder.aggressive
|
||||
@@ -59,6 +61,12 @@ describe WPScan::Finders::Users::OembedApi do
|
||||
expect(user.interesting_entries).to eql ['http://wp.lab/wp-json/oembed/1.0/embed?url=http://wp.lab/&format=json']
|
||||
end
|
||||
end
|
||||
|
||||
context 'when body is an array' do
|
||||
let(:fixture) { fixtures.join('array.json') }
|
||||
|
||||
its(:aggressive) { should eql([]) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -87,6 +87,8 @@ describe WPScan::Finders::Users::WpJsonApi do
|
||||
describe '#api_url' do
|
||||
let(:fixtures) { super().join('api_url') }
|
||||
|
||||
before { allow(target).to receive(:sub_dir).and_return(false) }
|
||||
|
||||
context 'when url in the homepage' do
|
||||
{
|
||||
in_scope: 'https://wp.lab/wp-json/wp/v2/users/',
|
||||
@@ -100,7 +102,7 @@ describe WPScan::Finders::Users::WpJsonApi do
|
||||
end
|
||||
|
||||
context 'when subdir' do
|
||||
before { allow(target).to receive(:subdir).and_return('cms') }
|
||||
before { allow(target).to receive(:sub_dir).and_return('cms') }
|
||||
|
||||
{
|
||||
in_scope_subdir: 'https://wp.lab/cms/wp-json/wp/v2/users/',
|
||||
|
||||
@@ -81,24 +81,39 @@ describe WPScan::Model::Plugin do
|
||||
end
|
||||
|
||||
describe '#latest_version, #last_updated, #popular' do
|
||||
context 'when none' do
|
||||
let(:slug) { 'vulnerable-not-popular' }
|
||||
before { allow(plugin).to receive(:db_data).and_return(db_data) }
|
||||
|
||||
context 'when no db_data and no metadata' do
|
||||
let(:slug) { 'not-known' }
|
||||
let(:db_data) { {} }
|
||||
|
||||
its(:latest_version) { should be_nil }
|
||||
its(:last_updated) { should be_nil }
|
||||
its(:popular?) { should be false }
|
||||
end
|
||||
|
||||
context 'when values' do
|
||||
context 'when no db_data but metadata' do
|
||||
let(:slug) { 'no-vulns-popular' }
|
||||
let(:db_data) { {} }
|
||||
|
||||
its(:latest_version) { should eql WPScan::Model::Version.new('2.0') }
|
||||
its(:last_updated) { should eql '2015-05-16T00:00:00.000Z' }
|
||||
its(:popular?) { should be true }
|
||||
end
|
||||
|
||||
context 'when db_data' do
|
||||
let(:slug) { 'no-vulns-popular' }
|
||||
let(:db_data) { vuln_api_data_for('plugins/no-vulns-popular') }
|
||||
|
||||
its(:latest_version) { should eql WPScan::Model::Version.new('2.1') }
|
||||
its(:last_updated) { should eql '2015-05-16T00:00:00.000Z-via-api' }
|
||||
its(:popular?) { should be true }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#outdated?' do
|
||||
before { allow(plugin).to receive(:db_data).and_return({}) }
|
||||
|
||||
context 'when last_version' do
|
||||
let(:slug) { 'no-vulns-popular' }
|
||||
|
||||
@@ -116,13 +131,13 @@ describe WPScan::Model::Plugin do
|
||||
.and_return(WPScan::Model::Version.new(version_number))
|
||||
end
|
||||
|
||||
context 'when version < last_version' do
|
||||
context 'when version < latest_version' do
|
||||
let(:version_number) { '1.2' }
|
||||
|
||||
its(:outdated?) { should eql true }
|
||||
end
|
||||
|
||||
context 'when version >= last_version' do
|
||||
context 'when version >= latest_version' do
|
||||
let(:version_number) { '3.0' }
|
||||
|
||||
its(:outdated?) { should eql false }
|
||||
@@ -130,7 +145,7 @@ describe WPScan::Model::Plugin do
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no last_version' do
|
||||
context 'when no latest_version' do
|
||||
let(:slug) { 'vulnerable-not-popular' }
|
||||
|
||||
context 'when no version' do
|
||||
@@ -153,13 +168,16 @@ describe WPScan::Model::Plugin do
|
||||
end
|
||||
|
||||
describe '#vulnerabilities' do
|
||||
before { allow(plugin).to receive(:db_data).and_return(db_data) }
|
||||
|
||||
after do
|
||||
expect(plugin.vulnerabilities).to eq @expected
|
||||
expect(plugin.vulnerable?).to eql @expected.empty? ? false : true
|
||||
end
|
||||
|
||||
context 'when plugin not in the DB' do
|
||||
let(:slug) { 'not-in-db' }
|
||||
let(:slug) { 'not-in-db' }
|
||||
let(:db_data) { {} }
|
||||
|
||||
it 'returns an empty array' do
|
||||
@expected = []
|
||||
@@ -168,7 +186,8 @@ describe WPScan::Model::Plugin do
|
||||
|
||||
context 'when in the DB' do
|
||||
context 'when no vulnerabilities' do
|
||||
let(:slug) { 'no-vulns-popular' }
|
||||
let(:slug) { 'no-vulns-popular' }
|
||||
let(:db_data) { vuln_api_data_for('plugins/no-vulns-popular') }
|
||||
|
||||
it 'returns an empty array' do
|
||||
@expected = []
|
||||
@@ -176,11 +195,13 @@ describe WPScan::Model::Plugin do
|
||||
end
|
||||
|
||||
context 'when vulnerabilities' do
|
||||
let(:slug) { 'vulnerable-not-popular' }
|
||||
let(:slug) { 'vulnerable-not-popular' }
|
||||
let(:db_data) { vuln_api_data_for('plugins/vulnerable-not-popular') }
|
||||
|
||||
let(:all_vulns) do
|
||||
[
|
||||
WPScan::Vulnerability.new(
|
||||
'First Vuln',
|
||||
'First Vuln <= 6.3.10 - LFI',
|
||||
{ wpvulndb: '1' },
|
||||
'LFI',
|
||||
'6.3.10'
|
||||
|
||||
@@ -10,10 +10,9 @@ describe WPScan::Model::Theme do
|
||||
before { expect(blog).to receive(:content_dir).at_least(1).and_return('wp-content') }
|
||||
|
||||
describe '#new' do
|
||||
before do
|
||||
stub_request(:get, /.*\.css\z/)
|
||||
.to_return(body: File.read(fixtures.join('style.css')))
|
||||
end
|
||||
before { stub_request(:get, /.*\.css\z/).to_return(body: File.read(fixture)) }
|
||||
|
||||
let(:fixture) { fixtures.join('style.css') }
|
||||
|
||||
its(:url) { should eql 'http://wp.lab/wp-content/themes/spec/' }
|
||||
its(:style_url) { should eql 'http://wp.lab/wp-content/themes/spec/style.css' }
|
||||
@@ -34,6 +33,14 @@ describe WPScan::Model::Theme do
|
||||
|
||||
its(:style_url) { should eql opts[:style_url] }
|
||||
end
|
||||
|
||||
context 'when some new lines are stripped' do
|
||||
let(:fixture) { fixtures.join('stripped_new_lines.css') }
|
||||
|
||||
its(:style_name) { should eql 'Divi' }
|
||||
its(:style_uri) { should eql 'http://www.elegantthemes.com/gallery/divi/' }
|
||||
its(:license_uri) { should eql 'http://www.gnu.org/licenses/gpl-2.0.html' }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#version' do
|
||||
@@ -86,8 +93,179 @@ describe WPScan::Model::Theme do
|
||||
end
|
||||
end
|
||||
|
||||
describe '#latest_version, #last_updated, #popular' do
|
||||
before do
|
||||
stub_request(:get, /.*\.css\z/)
|
||||
allow(theme).to receive(:db_data).and_return(db_data)
|
||||
end
|
||||
|
||||
context 'when no db_data and no metadata' do
|
||||
let(:slug) { 'not-known' }
|
||||
let(:db_data) { {} }
|
||||
|
||||
its(:latest_version) { should be_nil }
|
||||
its(:last_updated) { should be_nil }
|
||||
its(:popular?) { should be false }
|
||||
end
|
||||
|
||||
context 'when no db_data but metadata' do
|
||||
let(:slug) { 'no-vulns-popular' }
|
||||
let(:db_data) { {} }
|
||||
|
||||
its(:latest_version) { should eql WPScan::Model::Version.new('2.0') }
|
||||
its(:last_updated) { should eql '2015-05-16T00:00:00.000Z' }
|
||||
its(:popular?) { should be true }
|
||||
end
|
||||
|
||||
context 'when db_data' do
|
||||
let(:slug) { 'no-vulns-popular' }
|
||||
let(:db_data) { vuln_api_data_for('themes/no-vulns-popular') }
|
||||
|
||||
its(:latest_version) { should eql WPScan::Model::Version.new('2.2') }
|
||||
its(:last_updated) { should eql '2015-05-16T00:00:00.000Z-via-api' }
|
||||
its(:popular?) { should be true }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#outdated?' do
|
||||
before do
|
||||
stub_request(:get, /.*\.css\z/)
|
||||
allow(theme).to receive(:db_data).and_return({})
|
||||
end
|
||||
|
||||
context 'when last_version' do
|
||||
let(:slug) { 'no-vulns-popular' }
|
||||
|
||||
context 'when no version' do
|
||||
before { expect(theme).to receive(:version).at_least(1).and_return(nil) }
|
||||
|
||||
its(:outdated?) { should eql false }
|
||||
end
|
||||
|
||||
context 'when version' do
|
||||
before do
|
||||
expect(theme)
|
||||
.to receive(:version)
|
||||
.at_least(1)
|
||||
.and_return(WPScan::Model::Version.new(version_number))
|
||||
end
|
||||
|
||||
context 'when version < latest_version' do
|
||||
let(:version_number) { '1.2' }
|
||||
|
||||
its(:outdated?) { should eql true }
|
||||
end
|
||||
|
||||
context 'when version >= latest_version' do
|
||||
let(:version_number) { '3.0' }
|
||||
|
||||
its(:outdated?) { should eql false }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no latest_version' do
|
||||
let(:slug) { 'vulnerable-not-popular' }
|
||||
|
||||
context 'when no version' do
|
||||
before { expect(theme).to receive(:version).at_least(1).and_return(nil) }
|
||||
|
||||
its(:outdated?) { should eql false }
|
||||
end
|
||||
|
||||
context 'when version' do
|
||||
before do
|
||||
expect(theme)
|
||||
.to receive(:version)
|
||||
.at_least(1)
|
||||
.and_return(WPScan::Model::Version.new('1.0'))
|
||||
end
|
||||
|
||||
its(:outdated?) { should eql false }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#vulnerabilities' do
|
||||
xit
|
||||
before do
|
||||
stub_request(:get, /.*\.css\z/)
|
||||
allow(theme).to receive(:db_data).and_return(db_data)
|
||||
end
|
||||
|
||||
after do
|
||||
expect(theme.vulnerabilities).to eq @expected
|
||||
expect(theme.vulnerable?).to eql @expected.empty? ? false : true
|
||||
end
|
||||
|
||||
context 'when theme not in the DB' do
|
||||
let(:slug) { 'not-in-db' }
|
||||
let(:db_data) { {} }
|
||||
|
||||
it 'returns an empty array' do
|
||||
@expected = []
|
||||
end
|
||||
end
|
||||
|
||||
context 'when in the DB' do
|
||||
context 'when no vulnerabilities' do
|
||||
let(:slug) { 'no-vulns-popular' }
|
||||
let(:db_data) { vuln_api_data_for('themes/no-vulns-popular') }
|
||||
|
||||
it 'returns an empty array' do
|
||||
@expected = []
|
||||
end
|
||||
end
|
||||
|
||||
context 'when vulnerabilities' do
|
||||
let(:slug) { 'vulnerable-not-popular' }
|
||||
let(:db_data) { vuln_api_data_for('themes/vulnerable-not-popular') }
|
||||
|
||||
let(:all_vulns) do
|
||||
[
|
||||
WPScan::Vulnerability.new(
|
||||
'First Vuln',
|
||||
{ wpvulndb: '1' },
|
||||
'LFI',
|
||||
'6.3.10'
|
||||
),
|
||||
WPScan::Vulnerability.new('No Fixed In', wpvulndb: '2')
|
||||
]
|
||||
end
|
||||
|
||||
context 'when no theme version' do
|
||||
before { expect(theme).to receive(:version).at_least(1).and_return(false) }
|
||||
|
||||
it 'returns all the vulnerabilities' do
|
||||
@expected = all_vulns
|
||||
end
|
||||
end
|
||||
|
||||
context 'when theme version' do
|
||||
before do
|
||||
expect(theme)
|
||||
.to receive(:version)
|
||||
.at_least(1)
|
||||
.and_return(WPScan::Model::Version.new(number))
|
||||
end
|
||||
|
||||
context 'when < to a fixed_in' do
|
||||
let(:number) { '5.0' }
|
||||
|
||||
it 'returns it' do
|
||||
@expected = all_vulns
|
||||
end
|
||||
end
|
||||
|
||||
context 'when >= to a fixed_in' do
|
||||
let(:number) { '6.3.10' }
|
||||
|
||||
it 'does not return it ' do
|
||||
@expected = [all_vulns.last]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#parent_theme' do
|
||||
|
||||
@@ -40,11 +40,13 @@ describe WPScan::Model::WpVersion do
|
||||
|
||||
describe '#vulnerabilities' do
|
||||
subject(:version) { described_class.new(number) }
|
||||
before { allow(version).to receive(:db_data).and_return(db_data) }
|
||||
|
||||
context 'when no vulns' do
|
||||
let(:number) { '4.4' }
|
||||
let(:db_data) { { 'vulnerabilities' => [] } }
|
||||
|
||||
its(:vulnerabilities) { should eql([]) }
|
||||
its(:vulnerabilities) { should be_empty }
|
||||
end
|
||||
|
||||
context 'when vulnerable' do
|
||||
@@ -53,13 +55,30 @@ describe WPScan::Model::WpVersion do
|
||||
expect(version).to be_vulnerable
|
||||
end
|
||||
|
||||
let(:all_vulns) do
|
||||
[
|
||||
WPScan::Vulnerability.new(
|
||||
'WP 3.8.1 - Vuln 1',
|
||||
{ wpvulndb: '1' },
|
||||
'SQLI'
|
||||
),
|
||||
WPScan::Vulnerability.new(
|
||||
'WP 3.8.1 - Vuln 2',
|
||||
{ url: %w[url-2 url-3], osvdb: %w[10], cve: %w[2014-0166], wpvulndb: '2' },
|
||||
nil,
|
||||
'3.8.2'
|
||||
)
|
||||
]
|
||||
end
|
||||
|
||||
context 'when a signle vuln' do
|
||||
let(:number) { '3.8' }
|
||||
let(:number) { '3.8.1' }
|
||||
let(:db_data) { vuln_api_data_for('wordpresses/38') }
|
||||
|
||||
it 'returns the expected result' do
|
||||
@expected = [WPScan::Vulnerability.new(
|
||||
'WP 3.8 - Vuln 1',
|
||||
{ url: %w[url-4], osvdb: %w[11], wpvulndb: '3' },
|
||||
{ url: %w[url-4], wpvulndb: '3' },
|
||||
'AUTHBYPASS'
|
||||
)]
|
||||
end
|
||||
@@ -67,6 +86,7 @@ describe WPScan::Model::WpVersion do
|
||||
|
||||
context 'when multiple vulns' do
|
||||
let(:number) { '3.8.1' }
|
||||
let(:db_data) { vuln_api_data_for('wordpresses/381') }
|
||||
|
||||
it 'returns the expected results' do
|
||||
@expected = [
|
||||
@@ -77,7 +97,7 @@ describe WPScan::Model::WpVersion do
|
||||
),
|
||||
WPScan::Vulnerability.new(
|
||||
'WP 3.8.1 - Vuln 2',
|
||||
{ url: %w[url-2 url-3], osvdb: %w[10], cve: %w[2014-0166], wpvulndb: '2' },
|
||||
{ url: %w[url-2 url-3], cve: %w[2014-0166], wpvulndb: '2' },
|
||||
nil,
|
||||
'3.8.2'
|
||||
)
|
||||
@@ -87,27 +107,30 @@ describe WPScan::Model::WpVersion do
|
||||
end
|
||||
end
|
||||
|
||||
describe '#release_date' do
|
||||
describe '#metadata, #release_date, #status' do
|
||||
subject(:version) { described_class.new('3.8.1') }
|
||||
|
||||
its(:release_date) { should eql '2014-01-23' }
|
||||
before { allow(version).to receive(:db_data).and_return(db_data) }
|
||||
|
||||
context 'when the version is not in the DB' do
|
||||
subject(:version) { described_class.new('3.8.2') }
|
||||
context 'when no db_data' do
|
||||
let(:db_data) { {} }
|
||||
|
||||
its(:release_date) { should eql 'Unknown' }
|
||||
its(:release_date) { should eql '2014-01-23' }
|
||||
its(:status) { should eql 'outdated' }
|
||||
|
||||
context 'when the version is not in the metadata' do
|
||||
subject(:version) { described_class.new('3.8.2') }
|
||||
|
||||
its(:release_date) { should eql 'Unknown' }
|
||||
its(:status) { should eql 'Unknown' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#status' do
|
||||
subject(:version) { described_class.new('3.8.1') }
|
||||
context 'when db_data' do
|
||||
let(:db_data) { vuln_api_data_for('wordpresses/381') }
|
||||
|
||||
its(:status) { should eql 'outdated' }
|
||||
|
||||
context 'when the version is not in the DB' do
|
||||
subject(:version) { described_class.new('3.8.2') }
|
||||
|
||||
its(:release_date) { should eql 'Unknown' }
|
||||
its(:release_date) { should eql '2014-01-23-via-api' }
|
||||
its(:status) { should eql 'outdated-via-api' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,6 +9,7 @@ describe 'App::Views' do
|
||||
# in the expected output.
|
||||
%i[JSON CliNoColour].each do |formatter|
|
||||
context "when #{formatter}" do
|
||||
it_behaves_like 'App::Views::VulnApi'
|
||||
it_behaves_like 'App::Views::WpVersion'
|
||||
it_behaves_like 'App::Views::MainTheme'
|
||||
it_behaves_like 'App::Views::Enumeration'
|
||||
|
||||
9047
spec/fixtures/db/dynamic_finders.yml
vendored
9047
spec/fixtures/db/dynamic_finders.yml
vendored
File diff suppressed because it is too large
Load Diff
56
spec/fixtures/db/metadata.json
vendored
Normal file
56
spec/fixtures/db/metadata.json
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"wordpress": {
|
||||
"4.0": {
|
||||
"release_date": "2014-09-04",
|
||||
"status": "latest"
|
||||
},
|
||||
"3.8.1": {
|
||||
"release_date": "2014-01-23",
|
||||
"status": "outdated"
|
||||
},
|
||||
"3.8": {
|
||||
"release_date": "2013-12-12",
|
||||
"status": "insecure"
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"no-vulns-popular": {
|
||||
"vulnerabilities": false,
|
||||
"popular": true,
|
||||
"latest_version": "2.0",
|
||||
"last_updated": "2015-05-16T00:00:00.000Z"
|
||||
},
|
||||
"vulnerable-not-popular": {
|
||||
"latest_version": null,
|
||||
"last_updated": null,
|
||||
"popular": false,
|
||||
"vulnerabilities": true
|
||||
}
|
||||
},
|
||||
"themes": {
|
||||
"no-vulns-popular": {
|
||||
"popular": true,
|
||||
"latest_version": "2.0",
|
||||
"last_updated": "2015-05-16T00:00:00.000Z",
|
||||
"vulnerabilities": false
|
||||
},
|
||||
"vulnerable-not-popular": {
|
||||
"latest_version": null,
|
||||
"last_updated": null,
|
||||
"popular": false,
|
||||
"vulnerabilities": true
|
||||
},
|
||||
"dignitas-themes": {
|
||||
"popular": true,
|
||||
"latest_version": null,
|
||||
"last_updated": null,
|
||||
"vulnerabilities" : true
|
||||
},
|
||||
"yaaburnee-themes": {
|
||||
"popular": false,
|
||||
"latest_version": null,
|
||||
"last_updated": null,
|
||||
"vulnerabilities" : true
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user