Compare commits
113 Commits
plugin-bac
...
v3.7.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
16a3d54cb6 | ||
|
|
9677dcd978 | ||
|
|
17ea42f918 | ||
|
|
bd8915918d | ||
|
|
91db6773a0 | ||
|
|
f50680b61f | ||
|
|
3fb5d33333 | ||
|
|
f70bbb2660 | ||
|
|
589c1ac9bb | ||
|
|
d458fa1b89 | ||
|
|
dc2c99434f | ||
|
|
bbf36562d0 | ||
|
|
c458edf3e4 | ||
|
|
99c2aaef7a | ||
|
|
921096ca10 | ||
|
|
b0fbd6fa36 | ||
|
|
21bd67c44f | ||
|
|
4f142985a2 | ||
|
|
bfa89b44bc | ||
|
|
eba876e72b | ||
|
|
f1a7413e20 | ||
|
|
4d32749489 | ||
|
|
d911a16684 | ||
|
|
d7193bc755 | ||
|
|
aee9ffdb9c | ||
|
|
1f627d5e49 | ||
|
|
bb67626d09 | ||
|
|
4e0153e94a | ||
|
|
065142ff19 | ||
|
|
8bb6fae52f | ||
|
|
8cb7b81903 | ||
|
|
cb214ccda9 | ||
|
|
3fa7b96f27 | ||
|
|
7c8e259072 | ||
|
|
743d067042 | ||
|
|
50ea410718 | ||
|
|
e71182aed2 | ||
|
|
97f7963e0b | ||
|
|
6cea6a10bd | ||
|
|
344d41e365 | ||
|
|
597a8adfed | ||
|
|
5682e5483a | ||
|
|
18779edd7d | ||
|
|
63aeaea77a | ||
|
|
f51e48cb40 | ||
|
|
193372c79c | ||
|
|
34d0afe7e5 | ||
|
|
d33a9dd56d | ||
|
|
af2be90176 | ||
|
|
701fb21544 | ||
|
|
c8f010d9a6 | ||
|
|
c1ca7580e2 | ||
|
|
11d3c2cbf1 |
11
.rubocop.yml
11
.rubocop.yml
@@ -8,10 +8,12 @@ ClassVars:
|
|||||||
Enabled: false
|
Enabled: false
|
||||||
LineLength:
|
LineLength:
|
||||||
Max: 120
|
Max: 120
|
||||||
MethodLength:
|
|
||||||
Max: 20
|
|
||||||
Lint/UriEscapeUnescape:
|
Lint/UriEscapeUnescape:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
MethodLength:
|
||||||
|
Max: 20
|
||||||
|
Exclude:
|
||||||
|
- 'app/controllers/enumeration/cli_options.rb'
|
||||||
Metrics/AbcSize:
|
Metrics/AbcSize:
|
||||||
Max: 25
|
Max: 25
|
||||||
Metrics/BlockLength:
|
Metrics/BlockLength:
|
||||||
@@ -19,9 +21,14 @@ Metrics/BlockLength:
|
|||||||
- 'spec/**/*'
|
- 'spec/**/*'
|
||||||
Metrics/ClassLength:
|
Metrics/ClassLength:
|
||||||
Max: 150
|
Max: 150
|
||||||
|
Exclude:
|
||||||
|
- 'app/controllers/enumeration/cli_options.rb'
|
||||||
Metrics/CyclomaticComplexity:
|
Metrics/CyclomaticComplexity:
|
||||||
Max: 8
|
Max: 8
|
||||||
Style/Documentation:
|
Style/Documentation:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
Style/FormatStringToken:
|
Style/FormatStringToken:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
Style/NumericPredicate:
|
||||||
|
Exclude:
|
||||||
|
- 'app/controllers/vuln_api.rb'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM ruby:2.6.2-alpine3.9 AS builder
|
FROM ruby:2.6.3-alpine AS builder
|
||||||
LABEL maintainer="WPScan Team <team@wpscan.org>"
|
LABEL maintainer="WPScan Team <team@wpscan.org>"
|
||||||
|
|
||||||
ARG BUNDLER_ARGS="--jobs=8 --without test development"
|
ARG BUNDLER_ARGS="--jobs=8 --without test development"
|
||||||
@@ -19,7 +19,7 @@ RUN rake install --trace
|
|||||||
RUN chmod -R a+r /usr/local/bundle
|
RUN chmod -R a+r /usr/local/bundle
|
||||||
|
|
||||||
|
|
||||||
FROM ruby:2.6.2-alpine3.9
|
FROM ruby:2.6.3-alpine
|
||||||
LABEL maintainer="WPScan Team <team@wpscan.org>"
|
LABEL maintainer="WPScan Team <team@wpscan.org>"
|
||||||
|
|
||||||
RUN adduser -h /wpscan -g WPScan -D wpscan
|
RUN adduser -h /wpscan -g WPScan -D wpscan
|
||||||
|
|||||||
47
README.md
47
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://badge.fury.io/rb/wpscan" target="_blank"><img src="https://badge.fury.io/rb/wpscan.svg"></a>
|
||||||
<a href="https://travis-ci.org/wpscanteam/wpscan" target="_blank"><img src="https://travis-ci.org/wpscanteam/wpscan.svg?branch=master"></a>
|
<a href="https://travis-ci.org/wpscanteam/wpscan" target="_blank"><img src="https://travis-ci.org/wpscanteam/wpscan.svg?branch=master"></a>
|
||||||
<a href="https://codeclimate.com/github/wpscanteam/wpscan" target="_blank"><img src="https://codeclimate.com/github/wpscanteam/wpscan/badges/gpa.svg"></a>
|
<a href="https://codeclimate.com/github/wpscanteam/wpscan" target="_blank"><img src="https://codeclimate.com/github/wpscanteam/wpscan/badges/gpa.svg"></a>
|
||||||
<a href="https://www.patreon.com/wpscan" target="_blank"><img src="https://img.shields.io/badge/patreon-donate-green.svg"></a>
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
# INSTALL
|
# INSTALL
|
||||||
@@ -30,6 +29,7 @@
|
|||||||
- Curl >= 7.21 - Recommended: latest
|
- Curl >= 7.21 - Recommended: latest
|
||||||
- The 7.29 has a segfault
|
- The 7.29 has a segfault
|
||||||
- RubyGems - Recommended: latest
|
- RubyGems - Recommended: latest
|
||||||
|
- Nokogiri might require packages to be installed via your package manager depending on your OS, see https://nokogiri.org/tutorials/installing_nokogiri.html
|
||||||
|
|
||||||
### From RubyGems (Recommended)
|
### From RubyGems (Recommended)
|
||||||
|
|
||||||
@@ -77,41 +77,60 @@ docker run -it --rm wpscanteam/wpscan --url https://target.tld/ --enumerate u1-1
|
|||||||
|
|
||||||
# Usage
|
# 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'.
|
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)
|
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
|
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 can load all options (including the --url) from configuration files, the following locations are checked (order: first to last):
|
||||||
|
|
||||||
- ~/.wpscan/cli_options.json
|
- ~/.wpscan/scan.json
|
||||||
- ~/.wpscan/cli_options.yml
|
- ~/.wpscan/scan.yml
|
||||||
- pwd/.wpscan/cli_options.json
|
- pwd/.wpscan/scan.json
|
||||||
- pwd/.wpscan/cli_options.yml
|
- 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:
|
e.g:
|
||||||
|
|
||||||
~/.wpscan/cli_options.yml:
|
~/.wpscan/scan.yml:
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
proxy: 'http://127.0.0.1:8080'
|
cli_options:
|
||||||
verbose: true
|
proxy: 'http://127.0.0.1:8080'
|
||||||
|
verbose: true
|
||||||
```
|
```
|
||||||
|
|
||||||
pwd/.wpscan/cli_options.yml:
|
pwd/.wpscan/scan.yml:
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
proxy: 'socks5://127.0.0.1:9090'
|
cli_options:
|
||||||
url: 'http://target.tld'
|
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```
|
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
|
```shell
|
||||||
wpscan --url https://target.tld/ --enumerate u
|
wpscan --url https://target.tld/ --enumerate u
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'controllers/core'
|
require_relative 'controllers/core'
|
||||||
|
require_relative 'controllers/vuln_api'
|
||||||
require_relative 'controllers/custom_directories'
|
require_relative 'controllers/custom_directories'
|
||||||
require_relative 'controllers/wp_version'
|
require_relative 'controllers/wp_version'
|
||||||
require_relative 'controllers/main_theme'
|
require_relative 'controllers/main_theme'
|
||||||
|
|||||||
@@ -7,15 +7,6 @@ module WPScan
|
|||||||
module Controller
|
module Controller
|
||||||
# Enumeration Controller
|
# Enumeration Controller
|
||||||
class Enumeration < CMSScanner::Controller::Base
|
class Enumeration < CMSScanner::Controller::Base
|
||||||
def before_scan
|
|
||||||
DB::DynamicFinders::Plugin.create_versions_finders
|
|
||||||
DB::DynamicFinders::Theme.create_versions_finders
|
|
||||||
|
|
||||||
# Force the Garbage Collector to run due to the above method being
|
|
||||||
# quite heavy in objects allocation
|
|
||||||
GC.start
|
|
||||||
end
|
|
||||||
|
|
||||||
def run
|
def run
|
||||||
enum = ParsedCli.enumerate || {}
|
enum = ParsedCli.enumerate || {}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ module WPScan
|
|||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Array<OptParseValidator::OptBase> ]
|
# @return [ Array<OptParseValidator::OptBase> ]
|
||||||
# rubocop:disable Metrics/MethodLength
|
|
||||||
def cli_enum_choices
|
def cli_enum_choices
|
||||||
[
|
[
|
||||||
OptMultiChoices.new(
|
OptMultiChoices.new(
|
||||||
@@ -19,10 +18,10 @@ module WPScan
|
|||||||
choices: {
|
choices: {
|
||||||
vp: OptBoolean.new(['--vulnerable-plugins']),
|
vp: OptBoolean.new(['--vulnerable-plugins']),
|
||||||
ap: OptBoolean.new(['--all-plugins']),
|
ap: OptBoolean.new(['--all-plugins']),
|
||||||
p: OptBoolean.new(['--plugins']),
|
p: OptBoolean.new(['--popular-plugins']),
|
||||||
vt: OptBoolean.new(['--vulnerable-themes']),
|
vt: OptBoolean.new(['--vulnerable-themes']),
|
||||||
at: OptBoolean.new(['--all-themes']),
|
at: OptBoolean.new(['--all-themes']),
|
||||||
t: OptBoolean.new(['--themes']),
|
t: OptBoolean.new(['--popular-themes']),
|
||||||
tt: OptBoolean.new(['--timthumbs']),
|
tt: OptBoolean.new(['--timthumbs']),
|
||||||
cb: OptBoolean.new(['--config-backups']),
|
cb: OptBoolean.new(['--config-backups']),
|
||||||
dbe: OptBoolean.new(['--db-exports']),
|
dbe: OptBoolean.new(['--db-exports']),
|
||||||
@@ -45,7 +44,6 @@ module WPScan
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
# rubocop:enable Metrics/MethodLength
|
|
||||||
|
|
||||||
# @return [ Array<OptParseValidator::OptBase> ]
|
# @return [ Array<OptParseValidator::OptBase> ]
|
||||||
def cli_plugins_opts
|
def cli_plugins_opts
|
||||||
@@ -67,6 +65,11 @@ module WPScan
|
|||||||
'Use the supplied mode to check plugins versions instead of the --detection-mode ' \
|
'Use the supplied mode to check plugins versions instead of the --detection-mode ' \
|
||||||
'or --plugins-detection modes.'],
|
'or --plugins-detection modes.'],
|
||||||
choices: %w[mixed passive aggressive], normalize: :to_sym, default: :mixed
|
choices: %w[mixed passive aggressive], normalize: :to_sym, default: :mixed
|
||||||
|
),
|
||||||
|
OptInteger.new(
|
||||||
|
['--plugins-threshold THRESHOLD',
|
||||||
|
'Raise an error when the number of detected plugins via known locations reaches the threshold. ' \
|
||||||
|
'Set to 0 to ignore the threshold.'], default: 100, advanced: true
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
@@ -91,6 +94,11 @@ module WPScan
|
|||||||
'Use the supplied mode to check themes versions instead of the --detection-mode ' \
|
'Use the supplied mode to check themes versions instead of the --detection-mode ' \
|
||||||
'or --themes-detection modes.'],
|
'or --themes-detection modes.'],
|
||||||
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
||||||
|
),
|
||||||
|
OptInteger.new(
|
||||||
|
['--themes-threshold THRESHOLD',
|
||||||
|
'Raise an error when the number of detected themes via known locations reaches the threshold. ' \
|
||||||
|
'Set to 0 to ignore the threshold.'], default: 20, advanced: true
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -56,12 +56,13 @@ module WPScan
|
|||||||
#
|
#
|
||||||
# @return [ Boolean ] Wether or not to enumerate the plugins
|
# @return [ Boolean ] Wether or not to enumerate the plugins
|
||||||
def enum_plugins?(opts)
|
def enum_plugins?(opts)
|
||||||
opts[:plugins] || opts[:all_plugins] || opts[:vulnerable_plugins]
|
opts[:popular_plugins] || opts[:all_plugins] || opts[:vulnerable_plugins]
|
||||||
end
|
end
|
||||||
|
|
||||||
def enum_plugins
|
def enum_plugins
|
||||||
opts = default_opts('plugins').merge(
|
opts = default_opts('plugins').merge(
|
||||||
list: plugins_list_from_opts(ParsedCli.options),
|
list: plugins_list_from_opts(ParsedCli.options),
|
||||||
|
threshold: ParsedCli.plugins_threshold,
|
||||||
sort: true
|
sort: true
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -91,7 +92,7 @@ module WPScan
|
|||||||
|
|
||||||
if opts[:enumerate][:all_plugins]
|
if opts[:enumerate][:all_plugins]
|
||||||
DB::Plugins.all_slugs
|
DB::Plugins.all_slugs
|
||||||
elsif opts[:enumerate][:plugins]
|
elsif opts[:enumerate][:popular_plugins]
|
||||||
DB::Plugins.popular_slugs
|
DB::Plugins.popular_slugs
|
||||||
else
|
else
|
||||||
DB::Plugins.vulnerable_slugs
|
DB::Plugins.vulnerable_slugs
|
||||||
@@ -102,12 +103,13 @@ module WPScan
|
|||||||
#
|
#
|
||||||
# @return [ Boolean ] Wether or not to enumerate the themes
|
# @return [ Boolean ] Wether or not to enumerate the themes
|
||||||
def enum_themes?(opts)
|
def enum_themes?(opts)
|
||||||
opts[:themes] || opts[:all_themes] || opts[:vulnerable_themes]
|
opts[:popular_themes] || opts[:all_themes] || opts[:vulnerable_themes]
|
||||||
end
|
end
|
||||||
|
|
||||||
def enum_themes
|
def enum_themes
|
||||||
opts = default_opts('themes').merge(
|
opts = default_opts('themes').merge(
|
||||||
list: themes_list_from_opts(ParsedCli.options),
|
list: themes_list_from_opts(ParsedCli.options),
|
||||||
|
threshold: ParsedCli.themes_threshold,
|
||||||
sort: true
|
sort: true
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -137,7 +139,7 @@ module WPScan
|
|||||||
|
|
||||||
if opts[:enumerate][:all_themes]
|
if opts[:enumerate][:all_themes]
|
||||||
DB::Themes.all_slugs
|
DB::Themes.all_slugs
|
||||||
elsif opts[:enumerate][:themes]
|
elsif opts[:enumerate][:popular_themes]
|
||||||
DB::Themes.popular_slugs
|
DB::Themes.popular_slugs
|
||||||
else
|
else
|
||||||
DB::Themes.vulnerable_slugs
|
DB::Themes.vulnerable_slugs
|
||||||
|
|||||||
@@ -65,30 +65,43 @@ module WPScan
|
|||||||
|
|
||||||
case ParsedCli.password_attack
|
case ParsedCli.password_attack
|
||||||
when :wp_login
|
when :wp_login
|
||||||
WPScan::Finders::Passwords::WpLogin.new(target)
|
Finders::Passwords::WpLogin.new(target)
|
||||||
when :xmlrpc
|
when :xmlrpc
|
||||||
raise Error::XMLRPCNotDetected unless xmlrpc
|
raise Error::XMLRPCNotDetected unless xmlrpc
|
||||||
|
|
||||||
WPScan::Finders::Passwords::XMLRPC.new(xmlrpc)
|
Finders::Passwords::XMLRPC.new(xmlrpc)
|
||||||
when :xmlrpc_multicall
|
when :xmlrpc_multicall
|
||||||
raise Error::XMLRPCNotDetected unless xmlrpc
|
raise Error::XMLRPCNotDetected unless xmlrpc
|
||||||
|
|
||||||
WPScan::Finders::Passwords::XMLRPCMulticall.new(xmlrpc)
|
Finders::Passwords::XMLRPCMulticall.new(xmlrpc)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Boolean ]
|
||||||
|
def xmlrpc_get_users_blogs_enabled?
|
||||||
|
if xmlrpc&.enabled? &&
|
||||||
|
xmlrpc.available_methods.include?('wp.getUsersBlogs') &&
|
||||||
|
xmlrpc.method_call('wp.getUsersBlogs', [SecureRandom.hex[0, 6], SecureRandom.hex[0, 4]])
|
||||||
|
.run.body !~ /XML\-RPC services are disabled/
|
||||||
|
|
||||||
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ CMSScanner::Finders::Finder ]
|
# @return [ CMSScanner::Finders::Finder ]
|
||||||
def attacker_from_automatic_detection
|
def attacker_from_automatic_detection
|
||||||
if xmlrpc&.enabled? && xmlrpc.available_methods.include?('wp.getUsersBlogs')
|
if xmlrpc_get_users_blogs_enabled?
|
||||||
wp_version = target.wp_version
|
wp_version = target.wp_version
|
||||||
|
|
||||||
if wp_version && wp_version < '4.4'
|
if wp_version && wp_version < '4.4'
|
||||||
WPScan::Finders::Passwords::XMLRPCMulticall.new(xmlrpc)
|
Finders::Passwords::XMLRPCMulticall.new(xmlrpc)
|
||||||
else
|
else
|
||||||
WPScan::Finders::Passwords::XMLRPC.new(xmlrpc)
|
Finders::Passwords::XMLRPC.new(xmlrpc)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
WPScan::Finders::Passwords::WpLogin.new(target)
|
Finders::Passwords::WpLogin.new(target)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
30
app/controllers/vuln_api.rb
Normal file
30
app/controllers/vuln_api.rb
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module WPScan
|
||||||
|
module Controller
|
||||||
|
# Controller to handle the API token
|
||||||
|
class VulnApi < CMSScanner::Controller::Base
|
||||||
|
def cli_options
|
||||||
|
[
|
||||||
|
OptString.new(['--api-token TOKEN', 'The WPVulnDB API Token to display vulnerability data'])
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
def before_scan
|
||||||
|
return unless ParsedCli.api_token
|
||||||
|
|
||||||
|
DB::VulnApi.token = ParsedCli.api_token
|
||||||
|
|
||||||
|
api_status = DB::VulnApi.status
|
||||||
|
|
||||||
|
raise Error::InvalidApiToken if api_status['error']
|
||||||
|
raise Error::ApiLimitReached if api_status['requests_remaining'] == 0
|
||||||
|
raise api_status['http_error'] if api_status['http_error']
|
||||||
|
end
|
||||||
|
|
||||||
|
def after_scan
|
||||||
|
output('status', status: DB::VulnApi.status, api_requests: WPScan.api_requests)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -17,7 +17,7 @@ module WPScan
|
|||||||
end
|
end
|
||||||
|
|
||||||
def before_scan
|
def before_scan
|
||||||
WPScan::DB::DynamicFinders::Wordpress.create_versions_finders
|
DB::DynamicFinders::Wordpress.create_versions_finders
|
||||||
end
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ module WPScan
|
|||||||
|
|
||||||
enumerate(potential_urls(opts), opts.merge(check_full_response: 200)) do |res|
|
enumerate(potential_urls(opts), opts.merge(check_full_response: 200)) do |res|
|
||||||
if res.effective_url.end_with?('.zip')
|
if res.effective_url.end_with?('.zip')
|
||||||
next unless res.headers['Content-Type'] =~ %r{\Aapplication/zip}i
|
next unless %r{\Aapplication/zip}i.match?(res.headers['Content-Type'])
|
||||||
else
|
else
|
||||||
next unless res.body =~ SQL_PATTERN
|
next unless SQL_PATTERN.match?(res.body)
|
||||||
end
|
end
|
||||||
|
|
||||||
found << Model::DbExport.new(res.request.url, found_by: DIRECT_ACCESS, confidence: 100)
|
found << Model::DbExport.new(res.request.url, found_by: DIRECT_ACCESS, confidence: 100)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ module WPScan
|
|||||||
def aggressive(_opts = {})
|
def aggressive(_opts = {})
|
||||||
path = 'installer-log.txt'
|
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(
|
Model::DuplicatorInstallerLog.new(
|
||||||
target.url(path),
|
target.url(path),
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ module WPScan
|
|||||||
pattern = %r{#{target.content_dir}/mu\-plugins/}i
|
pattern = %r{#{target.content_dir}/mu\-plugins/}i
|
||||||
|
|
||||||
target.in_scope_uris(target.homepage_res) do |uri|
|
target.in_scope_uris(target.homepage_res) do |uri|
|
||||||
next unless uri.path =~ pattern
|
next unless uri.path&.match?(pattern)
|
||||||
|
|
||||||
url = target.url('wp-content/mu-plugins/')
|
url = target.url('wp-content/mu-plugins/')
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ module WPScan
|
|||||||
path = 'wp-content/uploads/dump.sql'
|
path = 'wp-content/uploads/dump.sql'
|
||||||
res = target.head_and_get(path, [200], get: { headers: { 'Range' => 'bytes=0-3000' } })
|
res = target.head_and_get(path, [200], get: { headers: { 'Range' => 'bytes=0-3000' } })
|
||||||
|
|
||||||
return unless res.body =~ SQL_PATTERN
|
return unless SQL_PATTERN.match?(res.body)
|
||||||
|
|
||||||
Model::UploadSQLDump.new(
|
Model::UploadSQLDump.new(
|
||||||
target.url(path),
|
target.url(path),
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ module WPScan
|
|||||||
|
|
||||||
def valid_credentials?(response)
|
def valid_credentials?(response)
|
||||||
response.code == 302 &&
|
response.code == 302 &&
|
||||||
response.headers['Set-Cookie']&.any? { |cookie| cookie =~ /wordpress_logged_in_/i }
|
[*response.headers['Set-Cookie']]&.any? { |cookie| cookie =~ /wordpress_logged_in_/i }
|
||||||
end
|
end
|
||||||
|
|
||||||
def errored_response?(response)
|
def errored_response?(response)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ module WPScan
|
|||||||
include CMSScanner::Finders::Finder::BreadthFirstDictionaryAttack
|
include CMSScanner::Finders::Finder::BreadthFirstDictionaryAttack
|
||||||
|
|
||||||
def login_request(username, password)
|
def login_request(username, password)
|
||||||
target.method_call('wp.getUsersBlogs', [username, password])
|
target.method_call('wp.getUsersBlogs', [username, password], cache_ttl: 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
def valid_credentials?(response)
|
def valid_credentials?(response)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ module WPScan
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
target.multi_call(methods).run
|
target.multi_call(methods, cache_ttl: 0).run
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ Array<Model::User> ] users
|
# @param [ Array<Model::User> ] users
|
||||||
|
|||||||
@@ -13,25 +13,15 @@ module WPScan
|
|||||||
def initialize(plugin)
|
def initialize(plugin)
|
||||||
finders << PluginVersion::Readme.new(plugin)
|
finders << PluginVersion::Readme.new(plugin)
|
||||||
|
|
||||||
load_specific_finders(plugin)
|
create_and_load_dynamic_versions_finders(plugin)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Load the finders associated with the plugin
|
# Create the dynamic version finders related to the plugin and register them
|
||||||
#
|
#
|
||||||
# @param [ Model::Plugin ] plugin
|
# @param [ Model::Plugin ] plugin
|
||||||
def load_specific_finders(plugin)
|
def create_and_load_dynamic_versions_finders(plugin)
|
||||||
module_name = plugin.classify
|
DB::DynamicFinders::Plugin.create_versions_finders(plugin.slug).each do |finder|
|
||||||
|
finders << finder.new(plugin)
|
||||||
return unless Finders::PluginVersion.constants.include?(module_name)
|
|
||||||
|
|
||||||
mod = Finders::PluginVersion.const_get(module_name)
|
|
||||||
|
|
||||||
mod.constants.each do |constant|
|
|
||||||
c = mod.const_get(constant)
|
|
||||||
|
|
||||||
next unless c.is_a?(Class)
|
|
||||||
|
|
||||||
finders << c.new(plugin)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ module WPScan
|
|||||||
|
|
||||||
# The target(plugin)#readme_url can't be used directly here
|
# The target(plugin)#readme_url can't be used directly here
|
||||||
# as if the --detection-mode is passive, it will always return nil
|
# as if the --detection-mode is passive, it will always return nil
|
||||||
Model::WpItem::READMES.each do |file|
|
target.potential_readme_filenames.each do |file|
|
||||||
res = target.head_and_get(file)
|
res = target.head_and_get(file)
|
||||||
|
|
||||||
next unless res.code == 200 && !(numbers = version_numbers(res.body)).empty?
|
next unless res.code == 200 && !(numbers = version_numbers(res.body)).empty?
|
||||||
@@ -52,7 +52,7 @@ module WPScan
|
|||||||
|
|
||||||
number = Regexp.last_match[1]
|
number = Regexp.last_match[1]
|
||||||
|
|
||||||
number if number =~ /[0-9]+/
|
number if /[0-9]+/.match?(number)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ String ] body
|
# @param [ String ] body
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ module WPScan
|
|||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
# Plugins finder from Dynamic Finder 'BodyPattern'
|
# Plugins finder from Dynamic Finder 'BodyPattern'
|
||||||
class BodyPattern < WPScan::Finders::DynamicFinder::WpItems::Finder
|
class BodyPattern < Finders::DynamicFinder::WpItems::Finder
|
||||||
DEFAULT_CONFIDENCE = 30
|
DEFAULT_CONFIDENCE = 30
|
||||||
|
|
||||||
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
||||||
@@ -15,7 +15,7 @@ module WPScan
|
|||||||
#
|
#
|
||||||
# @return [ Plugin ] The detected plugin in the response, related to the config
|
# @return [ Plugin ] The detected plugin in the response, related to the config
|
||||||
def process_response(opts, response, slug, klass, config)
|
def process_response(opts, response, slug, klass, config)
|
||||||
return unless response.body =~ config['pattern']
|
return unless response.body&.match?(config['pattern'])
|
||||||
|
|
||||||
Model::Plugin.new(
|
Model::Plugin.new(
|
||||||
slug,
|
slug,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ module WPScan
|
|||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
# Plugins finder from the Dynamic Finder 'Comment'
|
# Plugins finder from the Dynamic Finder 'Comment'
|
||||||
class Comment < WPScan::Finders::DynamicFinder::WpItems::Finder
|
class Comment < Finders::DynamicFinder::WpItems::Finder
|
||||||
DEFAULT_CONFIDENCE = 30
|
DEFAULT_CONFIDENCE = 30
|
||||||
|
|
||||||
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
||||||
@@ -18,7 +18,7 @@ module WPScan
|
|||||||
response.html.xpath(config['xpath'] || '//comment()').each do |node|
|
response.html.xpath(config['xpath'] || '//comment()').each do |node|
|
||||||
comment = node.text.to_s.strip
|
comment = node.text.to_s.strip
|
||||||
|
|
||||||
next unless comment =~ config['pattern']
|
next unless comment&.match?(config['pattern'])
|
||||||
|
|
||||||
return Model::Plugin.new(
|
return Model::Plugin.new(
|
||||||
slug,
|
slug,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ module WPScan
|
|||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
# Plugins finder from Dynamic Finder 'ConfigParser'
|
# Plugins finder from Dynamic Finder 'ConfigParser'
|
||||||
class ConfigParser < WPScan::Finders::DynamicFinder::WpItems::Finder
|
class ConfigParser < Finders::DynamicFinder::WpItems::Finder
|
||||||
DEFAULT_CONFIDENCE = 40
|
DEFAULT_CONFIDENCE = 40
|
||||||
|
|
||||||
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ module WPScan
|
|||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
# Plugins finder from Dynamic Finder 'HeaderPattern'
|
# Plugins finder from Dynamic Finder 'HeaderPattern'
|
||||||
class HeaderPattern < WPScan::Finders::DynamicFinder::WpItems::Finder
|
class HeaderPattern < Finders::DynamicFinder::WpItems::Finder
|
||||||
DEFAULT_CONFIDENCE = 30
|
DEFAULT_CONFIDENCE = 30
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ module WPScan
|
|||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
# Plugins finder from the Dynamic Finder 'JavascriptVar'
|
# Plugins finder from the Dynamic Finder 'JavascriptVar'
|
||||||
class JavascriptVar < WPScan::Finders::DynamicFinder::WpItems::Finder
|
class JavascriptVar < Finders::DynamicFinder::WpItems::Finder
|
||||||
DEFAULT_CONFIDENCE = 60
|
DEFAULT_CONFIDENCE = 60
|
||||||
|
|
||||||
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ module WPScan
|
|||||||
|
|
||||||
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |_res, slug|
|
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |_res, slug|
|
||||||
found << Model::Plugin.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
found << Model::Plugin.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
||||||
|
|
||||||
|
raise Error::PluginsThresholdReached if opts[:threshold].positive? && found.size >= opts[:threshold]
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ module WPScan
|
|||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
# Plugins finder from Dynamic Finder 'QueryParameter'
|
# Plugins finder from Dynamic Finder 'QueryParameter'
|
||||||
class QueryParameter < WPScan::Finders::DynamicFinder::WpItems::Finder
|
class QueryParameter < Finders::DynamicFinder::WpItems::Finder
|
||||||
DEFAULT_CONFIDENCE = 10
|
DEFAULT_CONFIDENCE = 10
|
||||||
|
|
||||||
def passive(_opts = {})
|
def passive(_opts = {})
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ module WPScan
|
|||||||
module Finders
|
module Finders
|
||||||
module Plugins
|
module Plugins
|
||||||
# Plugins finder from the Dynamic Finder 'Xpath'
|
# Plugins finder from the Dynamic Finder 'Xpath'
|
||||||
class Xpath < WPScan::Finders::DynamicFinder::WpItems::Finder
|
class Xpath < Finders::DynamicFinder::WpItems::Finder
|
||||||
DEFAULT_CONFIDENCE = 40
|
DEFAULT_CONFIDENCE = 40
|
||||||
|
|
||||||
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
||||||
|
|||||||
@@ -16,25 +16,15 @@ module WPScan
|
|||||||
ThemeVersion::Style.new(theme) <<
|
ThemeVersion::Style.new(theme) <<
|
||||||
ThemeVersion::WooFrameworkMetaGenerator.new(theme)
|
ThemeVersion::WooFrameworkMetaGenerator.new(theme)
|
||||||
|
|
||||||
load_specific_finders(theme)
|
create_and_load_dynamic_versions_finders(theme)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Load the finders associated with the theme
|
# Create the dynamic version finders related to the theme and register them
|
||||||
#
|
#
|
||||||
# @param [ Model::Theme ] theme
|
# @param [ Model::Theme ] theme
|
||||||
def load_specific_finders(theme)
|
def create_and_load_dynamic_versions_finders(theme)
|
||||||
module_name = theme.classify
|
DB::DynamicFinders::Theme.create_versions_finders(theme.slug).each do |finder|
|
||||||
|
finders << finder.new(theme)
|
||||||
return unless Finders::ThemeVersion.constants.include?(module_name)
|
|
||||||
|
|
||||||
mod = Finders::ThemeVersion.const_get(module_name)
|
|
||||||
|
|
||||||
mod.constants.each do |constant|
|
|
||||||
c = mod.const_get(constant)
|
|
||||||
|
|
||||||
next unless c.is_a?(Class)
|
|
||||||
|
|
||||||
finders << c.new(theme)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ module WPScan
|
|||||||
|
|
||||||
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |_res, slug|
|
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |_res, slug|
|
||||||
found << Model::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
found << Model::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
||||||
|
|
||||||
|
raise Error::ThemesThresholdReached if opts[:threshold].positive? && found.size >= opts[:threshold]
|
||||||
end
|
end
|
||||||
|
|
||||||
found
|
found
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ module WPScan
|
|||||||
found = []
|
found = []
|
||||||
|
|
||||||
enumerate(target_urls(opts), opts.merge(check_full_response: 400)) do |res|
|
enumerate(target_urls(opts), opts.merge(check_full_response: 400)) do |res|
|
||||||
next unless 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))
|
found << Model::Timthumb.new(res.request.url, opts.merge(found_by: found_by, confidence: 100))
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ module WPScan
|
|||||||
|
|
||||||
return found if error.empty? # Protection plugin / error disabled
|
return found if error.empty? # Protection plugin / error disabled
|
||||||
|
|
||||||
next unless 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)
|
found << Model::User.new(username, found_by: found_by, confidence: 100)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ module WPScan
|
|||||||
# Users disclosed from the dc:creator field in the RSS
|
# Users disclosed from the dc:creator field in the RSS
|
||||||
# The names disclosed are display names, however depending on the configuration of the blog,
|
# The names disclosed are display names, however depending on the configuration of the blog,
|
||||||
# they can be the same than usernames
|
# they can be the same than usernames
|
||||||
class RSSGenerator < WPScan::Finders::WpVersion::RSSGenerator
|
class RSSGenerator < Finders::WpVersion::RSSGenerator
|
||||||
def process_urls(urls, _opts = {})
|
def process_urls(urls, _opts = {})
|
||||||
found = []
|
found = []
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ module WPScan
|
|||||||
# @param [ WPScan::Target ] target
|
# @param [ WPScan::Target ] target
|
||||||
def initialize(target)
|
def initialize(target)
|
||||||
(%w[RSSGenerator AtomGenerator RDFGenerator] +
|
(%w[RSSGenerator AtomGenerator RDFGenerator] +
|
||||||
WPScan::DB::DynamicFinders::Wordpress.versions_finders_configs.keys +
|
DB::DynamicFinders::Wordpress.versions_finders_configs.keys +
|
||||||
%w[Readme UniqueFingerprinting]
|
%w[Readme UniqueFingerprinting]
|
||||||
).each do |finder_name|
|
).each do |finder_name|
|
||||||
finders << WpVersion.const_get(finder_name.to_sym).new(target)
|
finders << WpVersion.const_get(finder_name.to_sym).new(target)
|
||||||
|
|||||||
@@ -15,9 +15,16 @@ module WPScan
|
|||||||
@uri = Addressable::URI.parse(blog.url(path_from_blog))
|
@uri = Addressable::URI.parse(blog.url(path_from_blog))
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ JSON ]
|
# Retrieve the metadata from the vuln API if available (and a valid token is given),
|
||||||
|
# or the local metadata db otherwise
|
||||||
|
# @return [ Hash ]
|
||||||
|
def metadata
|
||||||
|
@metadata ||= db_data.empty? ? DB::Plugin.metadata_at(slug) : db_data
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Hash ]
|
||||||
def db_data
|
def db_data
|
||||||
@db_data ||= DB::Plugin.db_data(slug)
|
@db_data ||= DB::VulnApi.plugin_data(slug)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
@@ -28,6 +35,11 @@ module WPScan
|
|||||||
|
|
||||||
@version
|
@version
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @return [ Array<String> ]
|
||||||
|
def potential_readme_filenames
|
||||||
|
@potential_readme_filenames ||= [*(DB::DynamicFinders::Plugin.df_data.dig(slug, 'Readme', 'path') || super)]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -21,9 +21,16 @@ module WPScan
|
|||||||
parse_style
|
parse_style
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Retrieve the metadata from the vuln API if available (and a valid token is given),
|
||||||
|
# or the local metadata db otherwise
|
||||||
# @return [ JSON ]
|
# @return [ JSON ]
|
||||||
|
def metadata
|
||||||
|
@metadata ||= db_data.empty? ? DB::Theme.metadata_at(slug) : db_data
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Hash ]
|
||||||
def db_data
|
def db_data
|
||||||
@db_data ||= DB::Theme.db_data(slug)
|
@db_data ||= DB::VulnApi.theme_data(slug)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ module WPScan
|
|||||||
include CMSScanner::Target::Platform::PHP
|
include CMSScanner::Target::Platform::PHP
|
||||||
include CMSScanner::Target::Server::Generic
|
include CMSScanner::Target::Server::Generic
|
||||||
|
|
||||||
|
# Most common readme filenames, based on checking all public plugins and themes.
|
||||||
READMES = %w[readme.txt README.txt README.md readme.md Readme.txt].freeze
|
READMES = %w[readme.txt README.txt README.md readme.md Readme.txt].freeze
|
||||||
|
|
||||||
attr_reader :uri, :slug, :detection_opts, :version_detection_opts, :blog, :path_from_blog, :db_data
|
attr_reader :uri, :slug, :detection_opts, :version_detection_opts, :blog, :path_from_blog, :db_data
|
||||||
@@ -59,18 +60,18 @@ module WPScan
|
|||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def latest_version
|
def latest_version
|
||||||
@latest_version ||= db_data['latest_version'] ? Model::Version.new(db_data['latest_version']) : nil
|
@latest_version ||= metadata['latest_version'] ? Model::Version.new(metadata['latest_version']) : nil
|
||||||
end
|
end
|
||||||
|
|
||||||
# Not used anywhere ATM
|
# Not used anywhere ATM
|
||||||
# @return [ Boolean ]
|
# @return [ Boolean ]
|
||||||
def popular?
|
def popular?
|
||||||
@popular ||= db_data['popular']
|
@popular ||= metadata['popular'] ? true : false
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def last_updated
|
def last_updated
|
||||||
@last_updated ||= db_data['last_updated']
|
@last_updated ||= metadata['last_updated']
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Boolean ]
|
# @return [ Boolean ]
|
||||||
@@ -117,7 +118,7 @@ module WPScan
|
|||||||
|
|
||||||
return @readme_url unless @readme_url.nil?
|
return @readme_url unless @readme_url.nil?
|
||||||
|
|
||||||
READMES.each do |path|
|
potential_readme_filenames.each do |path|
|
||||||
t_url = url(path)
|
t_url = url(path)
|
||||||
|
|
||||||
return @readme_url = t_url if Browser.forge_request(t_url, blog.head_or_get_params).run.code == 200
|
return @readme_url = t_url if Browser.forge_request(t_url, blog.head_or_get_params).run.code == 200
|
||||||
@@ -126,6 +127,10 @@ module WPScan
|
|||||||
@readme_url = false
|
@readme_url = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def potential_readme_filenames
|
||||||
|
@potential_readme_filenames ||= READMES
|
||||||
|
end
|
||||||
|
|
||||||
# @param [ String ] path
|
# @param [ String ] path
|
||||||
# @param [ Hash ] params The request params
|
# @param [ Hash ] params The request params
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -35,9 +35,16 @@ module WPScan
|
|||||||
@all_numbers.sort! { |a, b| Gem::Version.new(b) <=> Gem::Version.new(a) }
|
@all_numbers.sort! { |a, b| Gem::Version.new(b) <=> Gem::Version.new(a) }
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ JSON ]
|
# Retrieve the metadata from the vuln API if available (and a valid token is given),
|
||||||
|
# or the local metadata db otherwise
|
||||||
|
# @return [ Hash ]
|
||||||
|
def metadata
|
||||||
|
@metadata ||= db_data.empty? ? DB::Version.metadata_at(number) : db_data
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ Hash ]
|
||||||
def db_data
|
def db_data
|
||||||
@db_data ||= DB::Version.db_data(number)
|
@db_data ||= DB::VulnApi.wordpress_data(number)
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Array<Vulnerability> ]
|
# @return [ Array<Vulnerability> ]
|
||||||
@@ -55,12 +62,12 @@ module WPScan
|
|||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def release_date
|
def release_date
|
||||||
@release_date ||= db_data['release_date'] || 'Unknown'
|
@release_date ||= metadata['release_date'] || 'Unknown'
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def status
|
def status
|
||||||
@status ||= db_data['status'] || 'Unknown'
|
@status ||= metadata['status'] || 'Unknown'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ _______________________________________________________________
|
|||||||
|
|
||||||
WordPress Security Scanner by the WPScan Team
|
WordPress Security Scanner by the WPScan Team
|
||||||
Version <%= WPScan::VERSION %>
|
Version <%= WPScan::VERSION %>
|
||||||
Sponsored by Sucuri - https://sucuri.net
|
<%= ' ' * ((63 - WPScan::DB::Sponsor.text.length)/2) + WPScan::DB::Sponsor.text %>
|
||||||
@_WPScan_, @ethicalhack3r, @erwan_lr, @_FireFart_
|
@_WPScan_, @ethicalhack3r, @erwan_lr, @_FireFart_
|
||||||
_______________________________________________________________
|
_______________________________________________________________
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<%= notice_icon %> Config Backup(s) Identified:
|
<%= notice_icon %> Config Backup(s) Identified:
|
||||||
<% @config_backups.each do |config_backup| -%>
|
<% @config_backups.each do |config_backup| -%>
|
||||||
|
|
||||||
<%= info_icon %> <%= config_backup %>
|
<%= critical_icon %> <%= config_backup %>
|
||||||
<%= render('@finding', item: config_backup) -%>
|
<%= render('@finding', item: config_backup) -%>
|
||||||
<% end -%>
|
<% end -%>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<%= notice_icon %> Db Export(s) Identified:
|
<%= notice_icon %> Db Export(s) Identified:
|
||||||
<% @db_exports.each do |db_export| -%>
|
<% @db_exports.each do |db_export| -%>
|
||||||
|
|
||||||
<%= info_icon %> <%= db_export %>
|
<%= critical_icon %> <%= db_export %>
|
||||||
<%= render('@finding', item: db_export) -%>
|
<%= render('@finding', item: db_export) -%>
|
||||||
<% end -%>
|
<% end -%>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
13
app/views/cli/vuln_api/status.erb
Normal file
13
app/views/cli/vuln_api/status.erb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<% unless @status.empty? -%>
|
||||||
|
<% if @status['http_error'] -%>
|
||||||
|
<%= critical_icon %> WPVulnDB API, <%= @status['http_error'].to_s %>
|
||||||
|
<% else -%>
|
||||||
|
<%= info_icon %> WPVulnDB API OK
|
||||||
|
| Plan: <%= @status['plan'] %>
|
||||||
|
| Requests Done (during the scan): <%= @api_requests %>
|
||||||
|
| Requests Remaining: <%= @status['requests_remaining'] %>
|
||||||
|
<% end -%>
|
||||||
|
<% else -%>
|
||||||
|
<%= warning_icon %> No WPVulnDB API Token given, as a result vulnerability data has not been output.
|
||||||
|
<%= warning_icon %> You can get a free API token with 50 daily requests by registering at https://wpvulndb.com/users/sign_up.
|
||||||
|
<% end -%>
|
||||||
@@ -7,5 +7,5 @@
|
|||||||
"@erwan_lr",
|
"@erwan_lr",
|
||||||
"@_FireFart_"
|
"@_FireFart_"
|
||||||
],
|
],
|
||||||
"sponsored_by": "Sucuri - https://sucuri.net"
|
"sponsor": <%= WPScan::DB::Sponsor.text.to_json %>
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,9 +11,10 @@
|
|||||||
}<% unless index == last_index %>,<% end -%>
|
}<% unless index == last_index %>,<% end -%>
|
||||||
<% end -%>
|
<% end -%>
|
||||||
<% end -%>
|
<% end -%>
|
||||||
},
|
}
|
||||||
"vulnerabilities": [
|
<% if @item.respond_to?(:vulnerabilities) -%>
|
||||||
<% if @item.respond_to?(:vulnerabilities) && !(vulns = @item.vulnerabilities).empty? -%>
|
,"vulnerabilities": [
|
||||||
|
<% unless (vulns = @item.vulnerabilities).empty? -%>
|
||||||
<% last_index = vulns.size - 1 -%>
|
<% last_index = vulns.size - 1 -%>
|
||||||
<% vulns.each_with_index do |v, index| -%>
|
<% vulns.each_with_index do |v, index| -%>
|
||||||
{
|
{
|
||||||
@@ -23,4 +24,5 @@
|
|||||||
}<% unless index == last_index -%>,<% end -%>
|
}<% unless index == last_index -%>,<% end -%>
|
||||||
<% end -%>
|
<% end -%>
|
||||||
<% end -%>
|
<% end -%>
|
||||||
]
|
]
|
||||||
|
<% end -%>
|
||||||
13
app/views/json/vuln_api/status.erb
Normal file
13
app/views/json/vuln_api/status.erb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
"vuln_api": {
|
||||||
|
<% unless @status.empty? -%>
|
||||||
|
<% if @status['http_error'] -%>
|
||||||
|
"http_error": <%= @status['http_error'].to_s.to_json %>
|
||||||
|
<% else -%>
|
||||||
|
"plan": <%= @status['plan'].to_json %>,
|
||||||
|
"requests_done_during_scan": <%= @api_requests.to_json %>,
|
||||||
|
"requests_remaining": <%= @status['requests_remaining'].to_json %>
|
||||||
|
<% end -%>
|
||||||
|
<% else -%>
|
||||||
|
"error": "No WPVulnDB API Token given, as a result vulnerability data has not been output.\nYou can get a free API token with 50 daily requests by registering at https://wpvulndb.com/users/sign_up."
|
||||||
|
<% end -%>
|
||||||
|
},
|
||||||
@@ -5,6 +5,7 @@ require 'wpscan'
|
|||||||
|
|
||||||
WPScan::Scan.new do |s|
|
WPScan::Scan.new do |s|
|
||||||
s.controllers <<
|
s.controllers <<
|
||||||
|
WPScan::Controller::VulnApi.new <<
|
||||||
WPScan::Controller::CustomDirectories.new <<
|
WPScan::Controller::CustomDirectories.new <<
|
||||||
WPScan::Controller::InterestingFindings.new <<
|
WPScan::Controller::InterestingFindings.new <<
|
||||||
WPScan::Controller::WpVersion.new <<
|
WPScan::Controller::WpVersion.new <<
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ require 'wpscan'
|
|||||||
report = MemoryProfiler.report(top: 15) do
|
report = MemoryProfiler.report(top: 15) do
|
||||||
WPScan::Scan.new do |s|
|
WPScan::Scan.new do |s|
|
||||||
s.controllers <<
|
s.controllers <<
|
||||||
|
WPScan::Controller::VulnApi.new <<
|
||||||
WPScan::Controller::CustomDirectories.new <<
|
WPScan::Controller::CustomDirectories.new <<
|
||||||
WPScan::Controller::InterestingFindings.new <<
|
WPScan::Controller::InterestingFindings.new <<
|
||||||
WPScan::Controller::WpVersion.new <<
|
WPScan::Controller::WpVersion.new <<
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ StackProf.run(mode: :cpu, out: '/tmp/stackprof-cpu.dump', interval: 500) do
|
|||||||
# require_relative 'wpscan' doesn't work
|
# require_relative 'wpscan' doesn't work
|
||||||
WPScan::Scan.new do |s|
|
WPScan::Scan.new do |s|
|
||||||
s.controllers <<
|
s.controllers <<
|
||||||
|
WPScan::Controller::VulnApi.new <<
|
||||||
WPScan::Controller::CustomDirectories.new <<
|
WPScan::Controller::CustomDirectories.new <<
|
||||||
WPScan::Controller::InterestingFindings.new <<
|
WPScan::Controller::InterestingFindings.new <<
|
||||||
WPScan::Controller::WpVersion.new <<
|
WPScan::Controller::WpVersion.new <<
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ require 'uri'
|
|||||||
require 'time'
|
require 'time'
|
||||||
require 'readline'
|
require 'readline'
|
||||||
require 'securerandom'
|
require 'securerandom'
|
||||||
|
# Monkey Patches/Fixes/Override
|
||||||
|
require 'wpscan/typhoeus/response' # Adds a from_vuln_api? method
|
||||||
# Custom Libs
|
# Custom Libs
|
||||||
require 'wpscan/helper'
|
require 'wpscan/helper'
|
||||||
require 'wpscan/db'
|
require 'wpscan/db'
|
||||||
@@ -38,12 +39,28 @@ module WPScan
|
|||||||
APP_DIR = Pathname.new(__FILE__).dirname.join('..', 'app').expand_path
|
APP_DIR = Pathname.new(__FILE__).dirname.join('..', 'app').expand_path
|
||||||
DB_DIR = Pathname.new(Dir.home).join('.wpscan', 'db')
|
DB_DIR = Pathname.new(Dir.home).join('.wpscan', 'db')
|
||||||
|
|
||||||
|
Typhoeus.on_complete do |response|
|
||||||
|
next if response.cached? || !response.from_vuln_api?
|
||||||
|
|
||||||
|
self.api_requests += 1
|
||||||
|
end
|
||||||
|
|
||||||
# Override, otherwise it would be returned as 'wp_scan'
|
# Override, otherwise it would be returned as 'wp_scan'
|
||||||
#
|
#
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def self.app_name
|
def self.app_name
|
||||||
'wpscan'
|
'wpscan'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @return [ Integer ]
|
||||||
|
def self.api_requests
|
||||||
|
@@api_requests ||= 0
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param [ Integer ] value
|
||||||
|
def self.api_requests=(value)
|
||||||
|
@@api_requests = value
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
require "#{WPScan::APP_DIR}/app"
|
require "#{WPScan::APP_DIR}/app"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ module WPScan
|
|||||||
|
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def default_user_agent
|
def default_user_agent
|
||||||
"WPScan v#{VERSION} (https://wpscan.org/)"
|
@default_user_agent ||= "WPScan v#{VERSION} (https://wpscan.org/)"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,9 +7,12 @@ require_relative 'db/plugins'
|
|||||||
require_relative 'db/themes'
|
require_relative 'db/themes'
|
||||||
require_relative 'db/plugin'
|
require_relative 'db/plugin'
|
||||||
require_relative 'db/theme'
|
require_relative 'db/theme'
|
||||||
|
require_relative 'db/sponsor'
|
||||||
require_relative 'db/wp_version'
|
require_relative 'db/wp_version'
|
||||||
require_relative 'db/fingerprints'
|
require_relative 'db/fingerprints'
|
||||||
|
|
||||||
|
require_relative 'db/vuln_api'
|
||||||
|
|
||||||
require_relative 'db/dynamic_finders/base'
|
require_relative 'db/dynamic_finders/base'
|
||||||
require_relative 'db/dynamic_finders/plugin'
|
require_relative 'db/dynamic_finders/plugin'
|
||||||
require_relative 'db/dynamic_finders/theme'
|
require_relative 'db/dynamic_finders/theme'
|
||||||
|
|||||||
@@ -5,18 +5,19 @@ module WPScan
|
|||||||
module DynamicFinders
|
module DynamicFinders
|
||||||
class Base
|
class Base
|
||||||
# @return [ String ]
|
# @return [ String ]
|
||||||
def self.db_file
|
def self.df_file
|
||||||
@db_file ||= DB_DIR.join('dynamic_finders.yml').to_s
|
@df_file ||= DB_DIR.join('dynamic_finders.yml').to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def self.db_data
|
def self.all_df_data
|
||||||
# true allows aliases to be loaded
|
@all_df_data ||= YAML.safe_load(File.read(df_file), [Regexp])
|
||||||
@db_data ||= YAML.safe_load(File.read(db_file), [Regexp], [], true)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Array<Symbol> ]
|
# @return [ Array<Symbol> ]
|
||||||
def self.allowed_classes
|
def self.allowed_classes
|
||||||
|
# The Readme is not put in there as it's not a Real DF, but rather using the DF system
|
||||||
|
# to get the list of potential filenames for a given slug
|
||||||
@allowed_classes ||= %i[Comment Xpath HeaderPattern BodyPattern JavascriptVar QueryParameter ConfigParser]
|
@allowed_classes ||= %i[Comment Xpath HeaderPattern BodyPattern JavascriptVar QueryParameter ConfigParser]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ module WPScan
|
|||||||
module DynamicFinders
|
module DynamicFinders
|
||||||
class Plugin < Base
|
class Plugin < Base
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def self.db_data
|
def self.df_data
|
||||||
@db_data ||= super['plugins'] || {}
|
@df_data ||= all_df_data['plugins'] || {}
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.version_finder_module
|
def self.version_finder_module
|
||||||
@@ -21,7 +21,7 @@ module WPScan
|
|||||||
|
|
||||||
return configs unless allowed_classes.include?(finder_class)
|
return configs unless allowed_classes.include?(finder_class)
|
||||||
|
|
||||||
db_data.each do |slug, finders|
|
df_data.each do |slug, finders|
|
||||||
# Quite sure better can be done with some kind of logic statement in the select
|
# Quite sure better can be done with some kind of logic statement in the select
|
||||||
fs = if aggressive
|
fs = if aggressive
|
||||||
finders.reject { |_f, c| c['path'].nil? }
|
finders.reject { |_f, c| c['path'].nil? }
|
||||||
@@ -48,7 +48,7 @@ module WPScan
|
|||||||
|
|
||||||
@versions_finders_configs = {}
|
@versions_finders_configs = {}
|
||||||
|
|
||||||
db_data.each do |slug, finders|
|
df_data.each do |slug, finders|
|
||||||
finders.each do |finder_name, config|
|
finders.each do |finder_name, config|
|
||||||
next unless config.key?('version')
|
next unless config.key?('version')
|
||||||
|
|
||||||
@@ -73,23 +73,33 @@ module WPScan
|
|||||||
version_finder_module.const_get(constant_name)
|
version_finder_module.const_get(constant_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.create_versions_finders
|
# Create the dynamic finders related to the given slug, and return the created classes
|
||||||
versions_finders_configs.each do |slug, finders|
|
#
|
||||||
mod = maybe_create_module(slug)
|
# @param [ String ] slug
|
||||||
|
#
|
||||||
|
# @return [ Array<Class> ] The created classes
|
||||||
|
def self.create_versions_finders(slug)
|
||||||
|
created = []
|
||||||
|
mod = maybe_create_module(slug)
|
||||||
|
|
||||||
finders.each do |finder_class, config|
|
versions_finders_configs[slug]&.each do |finder_class, config|
|
||||||
klass = config['class'] || finder_class
|
klass = config['class'] || finder_class
|
||||||
|
|
||||||
# Instead of raising exceptions, skip unallowed/already defined finders
|
# Instead of raising exceptions, skip unallowed/already defined finders
|
||||||
# So that, when new DF configs are put in the .yml
|
# So that, when new DF configs are put in the .yml
|
||||||
# users with old version of WPScan will still be able to scan blogs
|
# users with old version of WPScan will still be able to scan blogs
|
||||||
# when updating the DB but not the tool
|
# when updating the DB but not the tool
|
||||||
next if mod.constants.include?(finder_class.to_sym) ||
|
|
||||||
!allowed_classes.include?(klass.to_sym)
|
|
||||||
|
|
||||||
version_finder_super_class(klass).create_child_class(mod, finder_class.to_sym, config)
|
next unless allowed_classes.include?(klass.to_sym)
|
||||||
end
|
|
||||||
|
created << if mod.constants.include?(finder_class.to_sym)
|
||||||
|
mod.const_get(finder_class.to_sym)
|
||||||
|
else
|
||||||
|
version_finder_super_class(klass).create_child_class(mod, finder_class.to_sym, config)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
created
|
||||||
end
|
end
|
||||||
|
|
||||||
# The idea here would be to check if the class exist in
|
# The idea here would be to check if the class exist in
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ module WPScan
|
|||||||
module DynamicFinders
|
module DynamicFinders
|
||||||
class Theme < Plugin
|
class Theme < Plugin
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def self.db_data
|
def self.df_data
|
||||||
@db_data ||= super['themes'] || {}
|
@df_data ||= all_df_data['themes'] || {}
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.version_finder_module
|
def self.version_finder_module
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ module WPScan
|
|||||||
module DynamicFinders
|
module DynamicFinders
|
||||||
class Wordpress < Base
|
class Wordpress < Base
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def self.db_data
|
def self.df_data
|
||||||
@db_data ||= super['wordpress'] || {}
|
@df_data ||= all_df_data['wordpress'] || {}
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Constant ]
|
# @return [ Constant ]
|
||||||
@@ -30,9 +30,9 @@ module WPScan
|
|||||||
return configs unless allowed_classes.include?(finder_class)
|
return configs unless allowed_classes.include?(finder_class)
|
||||||
|
|
||||||
finders = if aggressive
|
finders = if aggressive
|
||||||
db_data.reject { |_f, c| c['path'].nil? }
|
df_data.reject { |_f, c| c['path'].nil? }
|
||||||
else
|
else
|
||||||
db_data.select { |_f, c| c['path'].nil? }
|
df_data.select { |_f, c| c['path'].nil? }
|
||||||
end
|
end
|
||||||
|
|
||||||
finders.each do |finder_name, config|
|
finders.each do |finder_name, config|
|
||||||
@@ -48,7 +48,7 @@ module WPScan
|
|||||||
|
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def self.versions_finders_configs
|
def self.versions_finders_configs
|
||||||
@versions_finders_configs ||= db_data.select { |_finder_name, config| config.key?('version') }
|
@versions_finders_configs ||= df_data.select { |_finder_name, config| config.key?('version') }
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.create_versions_finders
|
def self.create_versions_finders
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ module WPScan
|
|||||||
module DB
|
module DB
|
||||||
# Plugin DB
|
# Plugin DB
|
||||||
class Plugin < WpItem
|
class Plugin < WpItem
|
||||||
# @return [ String ]
|
# @return [ Hash ]
|
||||||
def self.db_file
|
def self.metadata
|
||||||
@db_file ||= DB_DIR.join('plugins.json').to_s
|
@metadata ||= super['plugins'] || {}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ module WPScan
|
|||||||
# WP Plugins
|
# WP Plugins
|
||||||
class Plugins < WpItems
|
class Plugins < WpItems
|
||||||
# @return [ JSON ]
|
# @return [ JSON ]
|
||||||
def self.db
|
def self.metadata
|
||||||
Plugin.db
|
Plugin.metadata
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
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
|
module DB
|
||||||
# Theme DB
|
# Theme DB
|
||||||
class Theme < WpItem
|
class Theme < WpItem
|
||||||
# @return [ String ]
|
# @return [ Hash ]
|
||||||
def self.db_file
|
def self.metadata
|
||||||
@db_file ||= DB_DIR.join('themes.json').to_s
|
@metadata ||= super['themes'] || {}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ module WPScan
|
|||||||
# WP Themes
|
# WP Themes
|
||||||
class Themes < WpItems
|
class Themes < WpItems
|
||||||
# @return [ JSON ]
|
# @return [ JSON ]
|
||||||
def self.db
|
def self.metadata
|
||||||
Theme.db
|
Theme.metadata
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ module WPScan
|
|||||||
class Updater
|
class Updater
|
||||||
# /!\ Might want to also update the Enumeration#cli_options when some filenames are changed here
|
# /!\ Might want to also update the Enumeration#cli_options when some filenames are changed here
|
||||||
FILES = %w[
|
FILES = %w[
|
||||||
plugins.json themes.json wordpresses.json
|
metadata.json wp_fingerprints.json
|
||||||
timthumbs-v3.txt config_backups.txt db_exports.txt
|
timthumbs-v3.txt config_backups.txt db_exports.txt
|
||||||
dynamic_finders.yml wp_fingerprints.json LICENSE
|
dynamic_finders.yml LICENSE sponsor.txt
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
OLD_FILES = %w[wordpress.db user-agents.txt dynamic_finders_01.yml].freeze
|
OLD_FILES = %w[
|
||||||
|
wordpress.db user-agents.txt dynamic_finders_01.yml
|
||||||
|
wordpresses.json plugins.json themes.json
|
||||||
|
].freeze
|
||||||
|
|
||||||
attr_reader :repo_directory
|
attr_reader :repo_directory
|
||||||
|
|
||||||
@@ -64,11 +67,12 @@ module WPScan
|
|||||||
# @return [ Hash ] The params for Typhoeus::Request
|
# @return [ Hash ] The params for Typhoeus::Request
|
||||||
# @note Those params can't be overriden by CLI options
|
# @note Those params can't be overriden by CLI options
|
||||||
def request_params
|
def request_params
|
||||||
{
|
@request_params ||= {
|
||||||
timeout: 600,
|
timeout: 600,
|
||||||
connecttimeout: 300,
|
connecttimeout: 300,
|
||||||
accept_encoding: 'gzip, deflate',
|
accept_encoding: 'gzip, deflate',
|
||||||
cache_ttl: 0
|
cache_ttl: 0,
|
||||||
|
headers: { 'User-Agent' => Browser.instance.default_user_agent, 'Referer' => nil }
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
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
|
class WpItem
|
||||||
# @param [ String ] identifier The plugin/theme slug or version number
|
# @param [ String ] identifier The plugin/theme slug or version number
|
||||||
#
|
#
|
||||||
# @return [ Hash ] The JSON data from the DB associated to the identifier
|
# @return [ Hash ] The JSON data from the metadata associated to the identifier
|
||||||
def self.db_data(identifier)
|
def self.metadata_at(identifier)
|
||||||
db[identifier] || {}
|
metadata[identifier] || {}
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ JSON ]
|
# @return [ JSON ]
|
||||||
def self.db
|
def self.metadata
|
||||||
@db ||= read_json_file(db_file)
|
@metadata ||= read_json_file(metadata_file)
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [ String ]
|
||||||
|
def self.metadata_file
|
||||||
|
@metadata_file ||= DB_DIR.join('metadata.json').to_s
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,17 +6,17 @@ module WPScan
|
|||||||
class WpItems
|
class WpItems
|
||||||
# @return [ Array<String> ] The slug of all items
|
# @return [ Array<String> ] The slug of all items
|
||||||
def self.all_slugs
|
def self.all_slugs
|
||||||
db.keys
|
metadata.keys
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Array<String> ] The slug of all popular items
|
# @return [ Array<String> ] The slug of all popular items
|
||||||
def self.popular_slugs
|
def self.popular_slugs
|
||||||
db.select { |_key, item| item['popular'] == true }.keys
|
metadata.select { |_key, item| item['popular'] == true }.keys
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Array<String> ] The slug of all vulnerable items
|
# @return [ Array<String> ] The slug of all vulnerable items
|
||||||
def self.vulnerable_slugs
|
def self.vulnerable_slugs
|
||||||
db.reject { |_key, item| item['vulnerabilities'].empty? }.keys
|
metadata.select { |_key, item| item['vulnerabilities'] == true }.keys
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ module WPScan
|
|||||||
module DB
|
module DB
|
||||||
# WP Version
|
# WP Version
|
||||||
class Version < WpItem
|
class Version < WpItem
|
||||||
# @return [ String ]
|
# @return [ Hash ]
|
||||||
def self.db_file
|
def self.metadata
|
||||||
@db_file ||= DB_DIR.join('wordpresses.json').to_s
|
@metadata ||= super['wordpress'] || {}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ module WPScan
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
require_relative 'errors/enumeration'
|
||||||
require_relative 'errors/http'
|
require_relative 'errors/http'
|
||||||
require_relative 'errors/update'
|
require_relative 'errors/update'
|
||||||
|
require_relative 'errors/vuln_api'
|
||||||
require_relative 'errors/wordpress'
|
require_relative 'errors/wordpress'
|
||||||
require_relative 'errors/xmlrpc'
|
require_relative 'errors/xmlrpc'
|
||||||
|
|||||||
21
lib/wpscan/errors/enumeration.rb
Normal file
21
lib/wpscan/errors/enumeration.rb
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module WPScan
|
||||||
|
module Error
|
||||||
|
class PluginsThresholdReached < Standard
|
||||||
|
def to_s
|
||||||
|
"The number of plugins detected reached the threshold of #{ParsedCli.plugins_threshold} " \
|
||||||
|
'which might indicate False Positive. It would be recommended to use the --exclude-content-based ' \
|
||||||
|
'option to ignore the bad responses.'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ThemesThresholdReached < Standard
|
||||||
|
def to_s
|
||||||
|
"The number of themes detected reached the threshold of #{ParsedCli.themes_threshold} " \
|
||||||
|
'which might indicate False Positive. It would be recommended to use the --exclude-content-based ' \
|
||||||
|
'option to ignore the bad responses.'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
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
|
||||||
@@ -4,9 +4,9 @@ module WPScan
|
|||||||
module Finders
|
module Finders
|
||||||
module DynamicFinder
|
module DynamicFinder
|
||||||
module Version
|
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
|
# an HTML doc and Xpath can't be used
|
||||||
class BodyPattern < WPScan::Finders::DynamicFinder::Version::Finder
|
class BodyPattern < Finders::DynamicFinder::Version::Finder
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def self.child_class_constants
|
def self.child_class_constants
|
||||||
@child_class_constants ||= super().merge(PATTERN: nil, CONFIDENCE: 60)
|
@child_class_constants ||= super().merge(PATTERN: nil, CONFIDENCE: 60)
|
||||||
@@ -16,7 +16,7 @@ module WPScan
|
|||||||
# @param [ Hash ] opts
|
# @param [ Hash ] opts
|
||||||
# @return [ Version ]
|
# @return [ Version ]
|
||||||
def find(response, _opts = {})
|
def find(response, _opts = {})
|
||||||
return unless response.body =~ self.class::PATTERN
|
return unless response.code != 404 && response.body =~ self.class::PATTERN
|
||||||
|
|
||||||
create_version(
|
create_version(
|
||||||
Regexp.last_match[:v],
|
Regexp.last_match[:v],
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ module WPScan
|
|||||||
module Version
|
module Version
|
||||||
# Version finder in Comment, which is basically an Xpath one with a default
|
# Version finder in Comment, which is basically an Xpath one with a default
|
||||||
# Xpath of //comment()
|
# Xpath of //comment()
|
||||||
class Comment < WPScan::Finders::DynamicFinder::Version::Xpath
|
class Comment < Finders::DynamicFinder::Version::Xpath
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def self.child_class_constants
|
def self.child_class_constants
|
||||||
@child_class_constants ||= super().merge(PATTERN: nil, XPATH: '//comment()')
|
@child_class_constants ||= super().merge(PATTERN: nil, XPATH: '//comment()')
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ module WPScan
|
|||||||
module Version
|
module Version
|
||||||
# Version finder using by parsing config files, such as composer.json
|
# Version finder using by parsing config files, such as composer.json
|
||||||
# and so on
|
# and so on
|
||||||
class ConfigParser < WPScan::Finders::DynamicFinder::Version::Finder
|
class ConfigParser < Finders::DynamicFinder::Version::Finder
|
||||||
ALLOWED_PARSERS = [JSON, YAML].freeze
|
ALLOWED_PARSERS = [JSON, YAML].freeze
|
||||||
|
|
||||||
def self.child_class_constants
|
def self.child_class_constants
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ module WPScan
|
|||||||
module DynamicFinder
|
module DynamicFinder
|
||||||
module Version
|
module Version
|
||||||
# Version finder using Header Pattern method
|
# Version finder using Header Pattern method
|
||||||
class HeaderPattern < WPScan::Finders::DynamicFinder::Version::Finder
|
class HeaderPattern < Finders::DynamicFinder::Version::Finder
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def self.child_class_constants
|
def self.child_class_constants
|
||||||
@child_class_constants ||= super().merge(HEADER: nil, PATTERN: nil, CONFIDENCE: 60)
|
@child_class_constants ||= super().merge(HEADER: nil, PATTERN: nil, CONFIDENCE: 60)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ module WPScan
|
|||||||
module DynamicFinder
|
module DynamicFinder
|
||||||
module Version
|
module Version
|
||||||
# Version finder using JavaScript Variable method
|
# Version finder using JavaScript Variable method
|
||||||
class JavascriptVar < WPScan::Finders::DynamicFinder::Version::Finder
|
class JavascriptVar < Finders::DynamicFinder::Version::Finder
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def self.child_class_constants
|
def self.child_class_constants
|
||||||
@child_class_constants ||= super().merge(
|
@child_class_constants ||= super().merge(
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ module WPScan
|
|||||||
module DynamicFinder
|
module DynamicFinder
|
||||||
module Version
|
module Version
|
||||||
# Version finder using QueryParameter method
|
# Version finder using QueryParameter method
|
||||||
class QueryParameter < WPScan::Finders::DynamicFinder::Version::Finder
|
class QueryParameter < Finders::DynamicFinder::Version::Finder
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def self.child_class_constants
|
def self.child_class_constants
|
||||||
@child_class_constants ||= super().merge(
|
@child_class_constants ||= super().merge(
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ module WPScan
|
|||||||
module DynamicFinder
|
module DynamicFinder
|
||||||
module Version
|
module Version
|
||||||
# Version finder using Xpath method
|
# Version finder using Xpath method
|
||||||
class Xpath < WPScan::Finders::DynamicFinder::Version::Finder
|
class Xpath < Finders::DynamicFinder::Version::Finder
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
def self.child_class_constants
|
def self.child_class_constants
|
||||||
@child_class_constants ||= super().merge(
|
@child_class_constants ||= super().merge(
|
||||||
|
|||||||
@@ -4,22 +4,22 @@ module WPScan
|
|||||||
module Finders
|
module Finders
|
||||||
module DynamicFinder
|
module DynamicFinder
|
||||||
module WpItemVersion
|
module WpItemVersion
|
||||||
class BodyPattern < WPScan::Finders::DynamicFinder::Version::BodyPattern
|
class BodyPattern < Finders::DynamicFinder::Version::BodyPattern
|
||||||
end
|
end
|
||||||
|
|
||||||
class Comment < WPScan::Finders::DynamicFinder::Version::Comment
|
class Comment < Finders::DynamicFinder::Version::Comment
|
||||||
end
|
end
|
||||||
|
|
||||||
class ConfigParser < WPScan::Finders::DynamicFinder::Version::ConfigParser
|
class ConfigParser < Finders::DynamicFinder::Version::ConfigParser
|
||||||
end
|
end
|
||||||
|
|
||||||
class HeaderPattern < WPScan::Finders::DynamicFinder::Version::HeaderPattern
|
class HeaderPattern < Finders::DynamicFinder::Version::HeaderPattern
|
||||||
end
|
end
|
||||||
|
|
||||||
class JavascriptVar < WPScan::Finders::DynamicFinder::Version::JavascriptVar
|
class JavascriptVar < Finders::DynamicFinder::Version::JavascriptVar
|
||||||
end
|
end
|
||||||
|
|
||||||
class QueryParameter < WPScan::Finders::DynamicFinder::Version::QueryParameter
|
class QueryParameter < Finders::DynamicFinder::Version::QueryParameter
|
||||||
# @return [ Regexp ]
|
# @return [ Regexp ]
|
||||||
def path_pattern
|
def path_pattern
|
||||||
# TODO: consider the target.blog.themes_dir if the target is a Theme (maybe implement a WpItem#item_dir ?)
|
# TODO: consider the target.blog.themes_dir if the target is a Theme (maybe implement a WpItem#item_dir ?)
|
||||||
@@ -37,7 +37,7 @@ module WPScan
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class Xpath < WPScan::Finders::DynamicFinder::Version::Xpath
|
class Xpath < Finders::DynamicFinder::Version::Xpath
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -12,23 +12,23 @@ module WPScan
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class BodyPattern < WPScan::Finders::DynamicFinder::Version::BodyPattern
|
class BodyPattern < Finders::DynamicFinder::Version::BodyPattern
|
||||||
include Finder
|
include Finder
|
||||||
end
|
end
|
||||||
|
|
||||||
class Comment < WPScan::Finders::DynamicFinder::Version::Comment
|
class Comment < Finders::DynamicFinder::Version::Comment
|
||||||
include Finder
|
include Finder
|
||||||
end
|
end
|
||||||
|
|
||||||
class HeaderPattern < WPScan::Finders::DynamicFinder::Version::HeaderPattern
|
class HeaderPattern < Finders::DynamicFinder::Version::HeaderPattern
|
||||||
include Finder
|
include Finder
|
||||||
end
|
end
|
||||||
|
|
||||||
class JavascriptVar < WPScan::Finders::DynamicFinder::Version::JavascriptVar
|
class JavascriptVar < Finders::DynamicFinder::Version::JavascriptVar
|
||||||
include Finder
|
include Finder
|
||||||
end
|
end
|
||||||
|
|
||||||
class QueryParameter < WPScan::Finders::DynamicFinder::Version::QueryParameter
|
class QueryParameter < Finders::DynamicFinder::Version::QueryParameter
|
||||||
include Finder
|
include Finder
|
||||||
|
|
||||||
# @return [ Hash ]
|
# @return [ Hash ]
|
||||||
|
|||||||
@@ -6,13 +6,15 @@ rescue StandardError => e
|
|||||||
raise "JSON parsing error in #{file} #{e}"
|
raise "JSON parsing error in #{file} #{e}"
|
||||||
end
|
end
|
||||||
|
|
||||||
# @return [ Symbol ]
|
# Sanitize and classify a slug
|
||||||
# @note As a class can not start with a digit or underscore, a D_ is
|
# @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
|
# put as a prefix in such case. Ugly but well :x
|
||||||
# Not only used to classify slugs though, but Dynamic Finder names as well
|
# Not only used to classify slugs though, but Dynamic Finder names as well
|
||||||
|
#
|
||||||
|
# @return [ Symbol ]
|
||||||
def classify_slug(slug)
|
def classify_slug(slug)
|
||||||
classified = slug.to_s.tr('-', '_').camelize.to_s
|
classified = slug.to_s.gsub(/[^a-z\d\-]/i, '-').gsub(/\-{1,}/, '_').camelize.to_s
|
||||||
classified = "D_#{classified}" if classified[0] =~ /\d/
|
classified = "D_#{classified}" if /\d/.match?(classified[0])
|
||||||
|
|
||||||
classified.to_sym
|
classified.to_sym
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ module WPScan
|
|||||||
end
|
end
|
||||||
|
|
||||||
homepage_res.html.css('meta[name="generator"]').each do |node|
|
homepage_res.html.css('meta[name="generator"]').each do |node|
|
||||||
return true if node['content'] =~ /wordpress/i
|
return true if /wordpress/i.match?(node['content'])
|
||||||
end
|
end
|
||||||
|
|
||||||
return true unless comments_from_page(/wordpress/i, homepage_res).empty?
|
return true unless comments_from_page(/wordpress/i, homepage_res).empty?
|
||||||
@@ -109,6 +109,7 @@ module WPScan
|
|||||||
Browser.instance.forge_request(
|
Browser.instance.forge_request(
|
||||||
login_url,
|
login_url,
|
||||||
method: :post,
|
method: :post,
|
||||||
|
cache_ttl: 0,
|
||||||
body: { log: username, pwd: password }
|
body: { log: username, pwd: password }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -99,20 +99,19 @@ module WPScan
|
|||||||
|
|
||||||
# @return [ String, False ] String of the sub_dir found, false otherwise
|
# @return [ String, False ] String of the sub_dir found, false otherwise
|
||||||
# @note: nil can not be returned here, otherwise if there is no sub_dir
|
# @note: nil can not be returned here, otherwise if there is no sub_dir
|
||||||
# the check would be done each time
|
# the check would be done each time, which would make enumeration of
|
||||||
|
# long list of items very slow to generate
|
||||||
def sub_dir
|
def sub_dir
|
||||||
unless @sub_dir
|
return @sub_dir unless @sub_dir.nil?
|
||||||
# url_pattern is from CMSScanner::Target
|
|
||||||
pattern = %r{#{url_pattern}(.+?)/(?:xmlrpc\.php|wp\-includes/)}i
|
|
||||||
|
|
||||||
in_scope_uris(homepage_res) do |uri|
|
# url_pattern is from CMSScanner::Target
|
||||||
return @sub_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
|
pattern = %r{#{url_pattern}(.+?)/(?:xmlrpc\.php|wp\-includes/)}i
|
||||||
end
|
|
||||||
|
|
||||||
@sub_dir = false
|
in_scope_uris(homepage_res) do |uri|
|
||||||
|
return @sub_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
|
||||||
end
|
end
|
||||||
|
|
||||||
@sub_dir
|
@sub_dir = false
|
||||||
end
|
end
|
||||||
|
|
||||||
# Override of the WebSite#url to consider the custom WP directories
|
# Override of the WebSite#url to consider the custom WP directories
|
||||||
|
|||||||
13
lib/wpscan/typhoeus/response.rb
Normal file
13
lib/wpscan/typhoeus/response.rb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Typhoeus
|
||||||
|
# Custom Response class
|
||||||
|
class Response
|
||||||
|
# @note: Ignores requests done to the /status endpoint of the API
|
||||||
|
#
|
||||||
|
# @return [ Boolean ]
|
||||||
|
def from_vuln_api?
|
||||||
|
effective_url.start_with?(WPScan::DB::VulnApi.uri.to_s) && !effective_url.include?('/status')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
|
|
||||||
# Version
|
# Version
|
||||||
module WPScan
|
module WPScan
|
||||||
VERSION = '3.5.3'
|
VERSION = '3.7.2'
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -70,8 +70,8 @@ describe WPScan::Controller::Enumeration do
|
|||||||
it 'contains the correct options' do
|
it 'contains the correct options' do
|
||||||
expect(controller.cli_options.map(&:to_sym)).to eql(
|
expect(controller.cli_options.map(&:to_sym)).to eql(
|
||||||
%i[enumerate exclude_content_based
|
%i[enumerate exclude_content_based
|
||||||
plugins_list plugins_detection plugins_version_all plugins_version_detection
|
plugins_list plugins_detection plugins_version_all plugins_version_detection plugins_threshold
|
||||||
themes_list themes_detection themes_version_all themes_version_detection
|
themes_list themes_detection themes_version_all themes_version_detection themes_threshold
|
||||||
timthumbs_list timthumbs_detection
|
timthumbs_list timthumbs_detection
|
||||||
config_backups_list config_backups_detection
|
config_backups_list config_backups_detection
|
||||||
db_exports_list db_exports_detection
|
db_exports_list db_exports_detection
|
||||||
@@ -102,15 +102,6 @@ describe WPScan::Controller::Enumeration do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#before_scan' do
|
|
||||||
it 'creates the Dynamic Finders' do
|
|
||||||
expect(WPScan::DB::DynamicFinders::Plugin).to receive(:create_versions_finders)
|
|
||||||
expect(WPScan::DB::DynamicFinders::Theme).to receive(:create_versions_finders)
|
|
||||||
|
|
||||||
controller.before_scan
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#run' do
|
describe '#run' do
|
||||||
context 'when no :enumerate' do
|
context 'when no :enumerate' do
|
||||||
before do
|
before do
|
||||||
|
|||||||
@@ -52,6 +52,60 @@ describe WPScan::Controller::PasswordAttack do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#xmlrpc_get_users_blogs_enabled?' do
|
||||||
|
before { expect(controller.target).to receive(:xmlrpc).and_return(xmlrpc) }
|
||||||
|
|
||||||
|
context 'when xmlrpc not found' do
|
||||||
|
let(:xmlrpc) { nil }
|
||||||
|
|
||||||
|
its(:xmlrpc_get_users_blogs_enabled?) { should be false }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when xmlrpc not enabled' do
|
||||||
|
let(:xmlrpc) { WPScan::Model::XMLRPC.new("#{target_url}xmlrpc.php") }
|
||||||
|
|
||||||
|
it 'returns false' do
|
||||||
|
expect(xmlrpc).to receive(:enabled?).and_return(false)
|
||||||
|
|
||||||
|
expect(controller.xmlrpc_get_users_blogs_enabled?).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when xmlrpc enabled' do
|
||||||
|
let(:xmlrpc) { WPScan::Model::XMLRPC.new("#{target_url}xmlrpc.php") }
|
||||||
|
|
||||||
|
before { expect(xmlrpc).to receive(:enabled?).and_return(true) }
|
||||||
|
|
||||||
|
context 'when wp.getUsersBlogs methods not listed' do
|
||||||
|
it 'returns false' do
|
||||||
|
expect(xmlrpc).to receive(:available_methods).and_return(%w[m1 m2])
|
||||||
|
|
||||||
|
expect(controller.xmlrpc_get_users_blogs_enabled?).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when wp.getUsersBlogs method listed' do
|
||||||
|
before { expect(xmlrpc).to receive(:available_methods).and_return(%w[wp.getUsersBlogs m2]) }
|
||||||
|
|
||||||
|
context 'when wp.getUsersBlogs method disabled' do
|
||||||
|
it 'returns false' do
|
||||||
|
stub_request(:post, xmlrpc.url).to_return(body: 'XML-RPC services are disabled on this site.')
|
||||||
|
|
||||||
|
expect(controller.xmlrpc_get_users_blogs_enabled?).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when wp.getUsersBlogs method enabled' do
|
||||||
|
it 'returns true' do
|
||||||
|
stub_request(:post, xmlrpc.url).to_return(body: 'Incorrect username or password.')
|
||||||
|
|
||||||
|
expect(controller.xmlrpc_get_users_blogs_enabled?).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#attacker' do
|
describe '#attacker' do
|
||||||
context 'when --password-attack provided' do
|
context 'when --password-attack provided' do
|
||||||
let(:cli_args) { "#{super()} --password-attack #{attack}" }
|
let(:cli_args) { "#{super()} --password-attack #{attack}" }
|
||||||
@@ -92,7 +146,7 @@ describe WPScan::Controller::PasswordAttack do
|
|||||||
before do
|
before do
|
||||||
expect(controller.target)
|
expect(controller.target)
|
||||||
.to receive(:xmlrpc)
|
.to receive(:xmlrpc)
|
||||||
.and_return(WPScan::Model::XMLRPC.new("#{target_url}/xmlrpc.php"))
|
.and_return(WPScan::Model::XMLRPC.new("#{target_url}xmlrpc.php"))
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when single xmlrpc' do
|
context 'when single xmlrpc' do
|
||||||
@@ -117,73 +171,50 @@ describe WPScan::Controller::PasswordAttack do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context 'when automatic detection' do
|
context 'when automatic detection' do
|
||||||
before { expect(controller.target).to receive(:xmlrpc).and_return(xmlrpc) }
|
context 'when xmlrpc_get_users_blogs_enabled? is false' do
|
||||||
|
|
||||||
context 'when xmlrpc not found' do
|
|
||||||
let(:xmlrpc) { nil }
|
|
||||||
|
|
||||||
it 'returns the WpLogin' do
|
it 'returns the WpLogin' do
|
||||||
expect(controller.attacker).to be_a WPScan::Finders::Passwords::WpLogin
|
expect(controller).to receive(:xmlrpc_get_users_blogs_enabled?).and_return(false)
|
||||||
expect(controller.attacker.target).to be_a WPScan::Target
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when xmlrpc not enabled' do
|
|
||||||
let(:xmlrpc) { WPScan::Model::XMLRPC.new("#{target_url}/xmlrpc.php") }
|
|
||||||
|
|
||||||
it 'returns the WpLogin' do
|
|
||||||
expect(xmlrpc).to receive(:enabled?).and_return(false)
|
|
||||||
|
|
||||||
expect(controller.attacker).to be_a WPScan::Finders::Passwords::WpLogin
|
expect(controller.attacker).to be_a WPScan::Finders::Passwords::WpLogin
|
||||||
expect(controller.attacker.target).to be_a WPScan::Target
|
expect(controller.attacker.target).to be_a WPScan::Target
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when xmlrpc enabled' do
|
context 'when xmlrpc_get_users_blogs_enabled? is true' do
|
||||||
let(:xmlrpc) { WPScan::Model::XMLRPC.new("#{target_url}/xmlrpc.php") }
|
before do
|
||||||
|
expect(controller).to receive(:xmlrpc_get_users_blogs_enabled?).and_return(true)
|
||||||
|
|
||||||
before { expect(xmlrpc).to receive(:enabled?).and_return(true) }
|
expect(controller.target)
|
||||||
|
.to receive(:xmlrpc).and_return(WPScan::Model::XMLRPC.new("#{target_url}xmlrpc.php"))
|
||||||
|
end
|
||||||
|
|
||||||
context 'when wp.getUsersBlogs methods not available' do
|
context 'when WP version not found' do
|
||||||
it 'returns the WpLogin' do
|
it 'returns the XMLRPC' do
|
||||||
expect(xmlrpc).to receive(:available_methods).and_return(%w[m1 m2])
|
expect(controller.target).to receive(:wp_version).and_return(false)
|
||||||
|
|
||||||
expect(controller.attacker).to be_a WPScan::Finders::Passwords::WpLogin
|
expect(controller.attacker).to be_a WPScan::Finders::Passwords::XMLRPC
|
||||||
expect(controller.attacker.target).to be_a WPScan::Target
|
expect(controller.attacker.target).to be_a WPScan::Model::XMLRPC
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when wp.getUsersBlogs method evailable' do
|
context 'when WP version found' do
|
||||||
before { expect(xmlrpc).to receive(:available_methods).and_return(%w[wp.getUsersBlogs m2]) }
|
before { expect(controller.target).to receive(:wp_version).and_return(wp_version) }
|
||||||
|
|
||||||
context 'when WP version not found' do
|
context 'when WP < 4.4' do
|
||||||
it 'returns the XMLRPC' do
|
let(:wp_version) { WPScan::Model::WpVersion.new('3.8.1') }
|
||||||
expect(controller.target).to receive(:wp_version).and_return(false)
|
|
||||||
|
|
||||||
expect(controller.attacker).to be_a WPScan::Finders::Passwords::XMLRPC
|
it 'returns the XMLRPCMulticall' do
|
||||||
|
expect(controller.attacker).to be_a WPScan::Finders::Passwords::XMLRPCMulticall
|
||||||
expect(controller.attacker.target).to be_a WPScan::Model::XMLRPC
|
expect(controller.attacker.target).to be_a WPScan::Model::XMLRPC
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when WP version found' do
|
context 'when WP >= 4.4' do
|
||||||
before { expect(controller.target).to receive(:wp_version).and_return(wp_version) }
|
let(:wp_version) { WPScan::Model::WpVersion.new('4.4') }
|
||||||
|
|
||||||
context 'when WP < 4.4' do
|
it 'returns the XMLRPC' do
|
||||||
let(:wp_version) { WPScan::Model::WpVersion.new('3.8.1') }
|
expect(controller.attacker).to be_a WPScan::Finders::Passwords::XMLRPC
|
||||||
|
expect(controller.attacker.target).to be_a WPScan::Model::XMLRPC
|
||||||
it 'returns the XMLRPCMulticall' do
|
|
||||||
expect(controller.attacker).to be_a WPScan::Finders::Passwords::XMLRPCMulticall
|
|
||||||
expect(controller.attacker.target).to be_a WPScan::Model::XMLRPC
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when WP >= 4.4' do
|
|
||||||
let(:wp_version) { WPScan::Model::WpVersion.new('4.4') }
|
|
||||||
|
|
||||||
it 'returns the XMLRPC' do
|
|
||||||
expect(controller.attacker).to be_a WPScan::Finders::Passwords::XMLRPC
|
|
||||||
expect(controller.attacker.target).to be_a WPScan::Model::XMLRPC
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
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
|
||||||
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
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# If this file is tested alone (rspec path-to-this-file), then there will be an error about
|
|
||||||
# constants not being intilialized. This is due to the Dynamic Finders.
|
|
||||||
|
|
||||||
describe WPScan::Finders::PluginVersion::Base do
|
describe WPScan::Finders::PluginVersion::Base do
|
||||||
subject(:plugin_version) { described_class.new(plugin) }
|
subject(:plugin_version) { described_class.new(plugin) }
|
||||||
let(:plugin) { WPScan::Model::Plugin.new(slug, target) }
|
let(:plugin) { WPScan::Model::Plugin.new(slug, target) }
|
||||||
@@ -15,7 +12,7 @@ describe WPScan::Finders::PluginVersion::Base do
|
|||||||
expect(plugin_version.finders.map { |f| f.class.to_s.demodulize }).to match_array @expected
|
expect(plugin_version.finders.map { |f| f.class.to_s.demodulize }).to match_array @expected
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when no related specific finders' do
|
context 'when no related dynamic finders' do
|
||||||
let(:slug) { 'spec' }
|
let(:slug) { 'spec' }
|
||||||
|
|
||||||
it 'contains the default finders' do
|
it 'contains the default finders' do
|
||||||
@@ -25,19 +22,13 @@ describe WPScan::Finders::PluginVersion::Base do
|
|||||||
|
|
||||||
# Dynamic Version Finders are not tested here, they are in
|
# Dynamic Version Finders are not tested here, they are in
|
||||||
# spec/lib/finders/dynamic_finder/plugin_versions_spec
|
# spec/lib/finders/dynamic_finder/plugin_versions_spec
|
||||||
context 'when specific finders' do
|
context 'when dynamic finders' do
|
||||||
let(:specific) do
|
|
||||||
{
|
|
||||||
# None so far
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
WPScan::DB::DynamicFinders::Plugin.versions_finders_configs.each do |plugin_slug, configs|
|
WPScan::DB::DynamicFinders::Plugin.versions_finders_configs.each do |plugin_slug, configs|
|
||||||
context "when #{plugin_slug} plugin" do
|
context "when #{plugin_slug} plugin" do
|
||||||
let(:slug) { plugin_slug }
|
let(:slug) { plugin_slug }
|
||||||
|
|
||||||
it 'contains the expected finders (default + specific + the dynamic ones)' do
|
it 'contains the expected finders (default + the dynamic ones)' do
|
||||||
@expected = default_finders + [*specific[plugin_slug]] + configs.keys
|
@expected = default_finders + configs.keys
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -13,20 +13,21 @@ describe WPScan::Finders::ThemeVersion::Base do
|
|||||||
expect(theme_version.finders.map { |f| f.class.to_s.demodulize }).to eql @expected
|
expect(theme_version.finders.map { |f| f.class.to_s.demodulize }).to eql @expected
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when no related specific finders' do
|
context 'when no related dynamic finders' do
|
||||||
it 'contains the default finders' do
|
it 'contains the default finders' do
|
||||||
@expected = default_finders
|
@expected = default_finders
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when specific finders' do
|
# Dynamic Version Finders are not tested here, they are in
|
||||||
{
|
# spec/lib/finders/dynamic_finder/theme_versions_spec
|
||||||
}.each do |theme_slug, specific_finders|
|
context 'when dynamic finders' do
|
||||||
|
WPScan::DB::DynamicFinders::Theme.versions_finders_configs.each do |theme_slug, configs|
|
||||||
context "when #{theme_slug} theme" do
|
context "when #{theme_slug} theme" do
|
||||||
let(:slug) { theme_slug }
|
let(:slug) { theme_slug }
|
||||||
|
|
||||||
it 'contains the expected finders' do
|
it 'contains the expected finders (default + the dynamic ones)' do
|
||||||
@expected = default_finders + specific_finders
|
@expected = default_finders + configs.keys
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -60,25 +60,60 @@ describe WPScan::Model::Plugin do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'potential_readme_filenames' do
|
||||||
|
context 'when not set in the DF file' do
|
||||||
|
its(:potential_readme_filenames) { should eql described_class::READMES }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when set in the DF file' do
|
||||||
|
context 'as a string' do
|
||||||
|
let(:slug) { 'photoblocks-grid-gallery' }
|
||||||
|
|
||||||
|
its(:potential_readme_filenames) { should eql %w[README.txt] }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'as an array' do
|
||||||
|
let(:slug) { 'customerlabs-actionrecorder' }
|
||||||
|
|
||||||
|
its(:potential_readme_filenames) { should eql %w[Readme.txt Readme.md] }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#latest_version, #last_updated, #popular' do
|
describe '#latest_version, #last_updated, #popular' do
|
||||||
context 'when none' do
|
before { allow(plugin).to receive(:db_data).and_return(db_data) }
|
||||||
let(:slug) { 'vulnerable-not-popular' }
|
|
||||||
|
context 'when no db_data and no metadata' do
|
||||||
|
let(:slug) { 'not-known' }
|
||||||
|
let(:db_data) { {} }
|
||||||
|
|
||||||
its(:latest_version) { should be_nil }
|
its(:latest_version) { should be_nil }
|
||||||
its(:last_updated) { should be_nil }
|
its(:last_updated) { should be_nil }
|
||||||
its(:popular?) { should be false }
|
its(:popular?) { should be false }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when values' do
|
context 'when no db_data but metadata' do
|
||||||
let(:slug) { 'no-vulns-popular' }
|
let(:slug) { 'no-vulns-popular' }
|
||||||
|
let(:db_data) { {} }
|
||||||
|
|
||||||
its(:latest_version) { should eql WPScan::Model::Version.new('2.0') }
|
its(:latest_version) { should eql WPScan::Model::Version.new('2.0') }
|
||||||
its(:last_updated) { should eql '2015-05-16T00:00:00.000Z' }
|
its(:last_updated) { should eql '2015-05-16T00:00:00.000Z' }
|
||||||
its(:popular?) { should be true }
|
its(:popular?) { should be true }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when db_data' do
|
||||||
|
let(:slug) { 'no-vulns-popular' }
|
||||||
|
let(:db_data) { vuln_api_data_for('plugins/no-vulns-popular') }
|
||||||
|
|
||||||
|
its(:latest_version) { should eql WPScan::Model::Version.new('2.1') }
|
||||||
|
its(:last_updated) { should eql '2015-05-16T00:00:00.000Z-via-api' }
|
||||||
|
its(:popular?) { should be true }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#outdated?' do
|
describe '#outdated?' do
|
||||||
|
before { allow(plugin).to receive(:db_data).and_return({}) }
|
||||||
|
|
||||||
context 'when last_version' do
|
context 'when last_version' do
|
||||||
let(:slug) { 'no-vulns-popular' }
|
let(:slug) { 'no-vulns-popular' }
|
||||||
|
|
||||||
@@ -96,13 +131,13 @@ describe WPScan::Model::Plugin do
|
|||||||
.and_return(WPScan::Model::Version.new(version_number))
|
.and_return(WPScan::Model::Version.new(version_number))
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when version < last_version' do
|
context 'when version < latest_version' do
|
||||||
let(:version_number) { '1.2' }
|
let(:version_number) { '1.2' }
|
||||||
|
|
||||||
its(:outdated?) { should eql true }
|
its(:outdated?) { should eql true }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when version >= last_version' do
|
context 'when version >= latest_version' do
|
||||||
let(:version_number) { '3.0' }
|
let(:version_number) { '3.0' }
|
||||||
|
|
||||||
its(:outdated?) { should eql false }
|
its(:outdated?) { should eql false }
|
||||||
@@ -110,7 +145,7 @@ describe WPScan::Model::Plugin do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when no last_version' do
|
context 'when no latest_version' do
|
||||||
let(:slug) { 'vulnerable-not-popular' }
|
let(:slug) { 'vulnerable-not-popular' }
|
||||||
|
|
||||||
context 'when no version' do
|
context 'when no version' do
|
||||||
@@ -133,13 +168,16 @@ describe WPScan::Model::Plugin do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe '#vulnerabilities' do
|
describe '#vulnerabilities' do
|
||||||
|
before { allow(plugin).to receive(:db_data).and_return(db_data) }
|
||||||
|
|
||||||
after do
|
after do
|
||||||
expect(plugin.vulnerabilities).to eq @expected
|
expect(plugin.vulnerabilities).to eq @expected
|
||||||
expect(plugin.vulnerable?).to eql @expected.empty? ? false : true
|
expect(plugin.vulnerable?).to eql @expected.empty? ? false : true
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when plugin not in the DB' do
|
context 'when plugin not in the DB' do
|
||||||
let(:slug) { 'not-in-db' }
|
let(:slug) { 'not-in-db' }
|
||||||
|
let(:db_data) { {} }
|
||||||
|
|
||||||
it 'returns an empty array' do
|
it 'returns an empty array' do
|
||||||
@expected = []
|
@expected = []
|
||||||
@@ -148,7 +186,8 @@ describe WPScan::Model::Plugin do
|
|||||||
|
|
||||||
context 'when in the DB' do
|
context 'when in the DB' do
|
||||||
context 'when no vulnerabilities' do
|
context 'when no vulnerabilities' do
|
||||||
let(:slug) { 'no-vulns-popular' }
|
let(:slug) { 'no-vulns-popular' }
|
||||||
|
let(:db_data) { vuln_api_data_for('plugins/no-vulns-popular') }
|
||||||
|
|
||||||
it 'returns an empty array' do
|
it 'returns an empty array' do
|
||||||
@expected = []
|
@expected = []
|
||||||
@@ -156,11 +195,13 @@ describe WPScan::Model::Plugin do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context 'when vulnerabilities' do
|
context 'when vulnerabilities' do
|
||||||
let(:slug) { 'vulnerable-not-popular' }
|
let(:slug) { 'vulnerable-not-popular' }
|
||||||
|
let(:db_data) { vuln_api_data_for('plugins/vulnerable-not-popular') }
|
||||||
|
|
||||||
let(:all_vulns) do
|
let(:all_vulns) do
|
||||||
[
|
[
|
||||||
WPScan::Vulnerability.new(
|
WPScan::Vulnerability.new(
|
||||||
'First Vuln',
|
'First Vuln <= 6.3.10 - LFI',
|
||||||
{ wpvulndb: '1' },
|
{ wpvulndb: '1' },
|
||||||
'LFI',
|
'LFI',
|
||||||
'6.3.10'
|
'6.3.10'
|
||||||
|
|||||||
@@ -86,8 +86,179 @@ describe WPScan::Model::Theme do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#latest_version, #last_updated, #popular' do
|
||||||
|
before do
|
||||||
|
stub_request(:get, /.*\.css\z/)
|
||||||
|
allow(theme).to receive(:db_data).and_return(db_data)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when no db_data and no metadata' do
|
||||||
|
let(:slug) { 'not-known' }
|
||||||
|
let(:db_data) { {} }
|
||||||
|
|
||||||
|
its(:latest_version) { should be_nil }
|
||||||
|
its(:last_updated) { should be_nil }
|
||||||
|
its(:popular?) { should be false }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when no db_data but metadata' do
|
||||||
|
let(:slug) { 'no-vulns-popular' }
|
||||||
|
let(:db_data) { {} }
|
||||||
|
|
||||||
|
its(:latest_version) { should eql WPScan::Model::Version.new('2.0') }
|
||||||
|
its(:last_updated) { should eql '2015-05-16T00:00:00.000Z' }
|
||||||
|
its(:popular?) { should be true }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when db_data' do
|
||||||
|
let(:slug) { 'no-vulns-popular' }
|
||||||
|
let(:db_data) { vuln_api_data_for('themes/no-vulns-popular') }
|
||||||
|
|
||||||
|
its(:latest_version) { should eql WPScan::Model::Version.new('2.2') }
|
||||||
|
its(:last_updated) { should eql '2015-05-16T00:00:00.000Z-via-api' }
|
||||||
|
its(:popular?) { should be true }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#outdated?' do
|
||||||
|
before do
|
||||||
|
stub_request(:get, /.*\.css\z/)
|
||||||
|
allow(theme).to receive(:db_data).and_return({})
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when last_version' do
|
||||||
|
let(:slug) { 'no-vulns-popular' }
|
||||||
|
|
||||||
|
context 'when no version' do
|
||||||
|
before { expect(theme).to receive(:version).at_least(1).and_return(nil) }
|
||||||
|
|
||||||
|
its(:outdated?) { should eql false }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when version' do
|
||||||
|
before do
|
||||||
|
expect(theme)
|
||||||
|
.to receive(:version)
|
||||||
|
.at_least(1)
|
||||||
|
.and_return(WPScan::Model::Version.new(version_number))
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when version < latest_version' do
|
||||||
|
let(:version_number) { '1.2' }
|
||||||
|
|
||||||
|
its(:outdated?) { should eql true }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when version >= latest_version' do
|
||||||
|
let(:version_number) { '3.0' }
|
||||||
|
|
||||||
|
its(:outdated?) { should eql false }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when no latest_version' do
|
||||||
|
let(:slug) { 'vulnerable-not-popular' }
|
||||||
|
|
||||||
|
context 'when no version' do
|
||||||
|
before { expect(theme).to receive(:version).at_least(1).and_return(nil) }
|
||||||
|
|
||||||
|
its(:outdated?) { should eql false }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when version' do
|
||||||
|
before do
|
||||||
|
expect(theme)
|
||||||
|
.to receive(:version)
|
||||||
|
.at_least(1)
|
||||||
|
.and_return(WPScan::Model::Version.new('1.0'))
|
||||||
|
end
|
||||||
|
|
||||||
|
its(:outdated?) { should eql false }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#vulnerabilities' do
|
describe '#vulnerabilities' do
|
||||||
xit
|
before do
|
||||||
|
stub_request(:get, /.*\.css\z/)
|
||||||
|
allow(theme).to receive(:db_data).and_return(db_data)
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
expect(theme.vulnerabilities).to eq @expected
|
||||||
|
expect(theme.vulnerable?).to eql @expected.empty? ? false : true
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when theme not in the DB' do
|
||||||
|
let(:slug) { 'not-in-db' }
|
||||||
|
let(:db_data) { {} }
|
||||||
|
|
||||||
|
it 'returns an empty array' do
|
||||||
|
@expected = []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when in the DB' do
|
||||||
|
context 'when no vulnerabilities' do
|
||||||
|
let(:slug) { 'no-vulns-popular' }
|
||||||
|
let(:db_data) { vuln_api_data_for('themes/no-vulns-popular') }
|
||||||
|
|
||||||
|
it 'returns an empty array' do
|
||||||
|
@expected = []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when vulnerabilities' do
|
||||||
|
let(:slug) { 'vulnerable-not-popular' }
|
||||||
|
let(:db_data) { vuln_api_data_for('themes/vulnerable-not-popular') }
|
||||||
|
|
||||||
|
let(:all_vulns) do
|
||||||
|
[
|
||||||
|
WPScan::Vulnerability.new(
|
||||||
|
'First Vuln',
|
||||||
|
{ wpvulndb: '1' },
|
||||||
|
'LFI',
|
||||||
|
'6.3.10'
|
||||||
|
),
|
||||||
|
WPScan::Vulnerability.new('No Fixed In', wpvulndb: '2')
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when no theme version' do
|
||||||
|
before { expect(theme).to receive(:version).at_least(1).and_return(false) }
|
||||||
|
|
||||||
|
it 'returns all the vulnerabilities' do
|
||||||
|
@expected = all_vulns
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when theme version' do
|
||||||
|
before do
|
||||||
|
expect(theme)
|
||||||
|
.to receive(:version)
|
||||||
|
.at_least(1)
|
||||||
|
.and_return(WPScan::Model::Version.new(number))
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when < to a fixed_in' do
|
||||||
|
let(:number) { '5.0' }
|
||||||
|
|
||||||
|
it 'returns it' do
|
||||||
|
@expected = all_vulns
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when >= to a fixed_in' do
|
||||||
|
let(:number) { '6.3.10' }
|
||||||
|
|
||||||
|
it 'does not return it ' do
|
||||||
|
@expected = [all_vulns.last]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#parent_theme' do
|
describe '#parent_theme' do
|
||||||
|
|||||||
@@ -40,11 +40,13 @@ describe WPScan::Model::WpVersion do
|
|||||||
|
|
||||||
describe '#vulnerabilities' do
|
describe '#vulnerabilities' do
|
||||||
subject(:version) { described_class.new(number) }
|
subject(:version) { described_class.new(number) }
|
||||||
|
before { allow(version).to receive(:db_data).and_return(db_data) }
|
||||||
|
|
||||||
context 'when no vulns' do
|
context 'when no vulns' do
|
||||||
let(:number) { '4.4' }
|
let(:number) { '4.4' }
|
||||||
|
let(:db_data) { { 'vulnerabilities' => [] } }
|
||||||
|
|
||||||
its(:vulnerabilities) { should eql([]) }
|
its(:vulnerabilities) { should be_empty }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when vulnerable' do
|
context 'when vulnerable' do
|
||||||
@@ -53,13 +55,30 @@ describe WPScan::Model::WpVersion do
|
|||||||
expect(version).to be_vulnerable
|
expect(version).to be_vulnerable
|
||||||
end
|
end
|
||||||
|
|
||||||
|
let(:all_vulns) do
|
||||||
|
[
|
||||||
|
WPScan::Vulnerability.new(
|
||||||
|
'WP 3.8.1 - Vuln 1',
|
||||||
|
{ wpvulndb: '1' },
|
||||||
|
'SQLI'
|
||||||
|
),
|
||||||
|
WPScan::Vulnerability.new(
|
||||||
|
'WP 3.8.1 - Vuln 2',
|
||||||
|
{ url: %w[url-2 url-3], osvdb: %w[10], cve: %w[2014-0166], wpvulndb: '2' },
|
||||||
|
nil,
|
||||||
|
'3.8.2'
|
||||||
|
)
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
context 'when a signle vuln' do
|
context 'when a signle vuln' do
|
||||||
let(:number) { '3.8' }
|
let(:number) { '3.8.1' }
|
||||||
|
let(:db_data) { vuln_api_data_for('wordpresses/38') }
|
||||||
|
|
||||||
it 'returns the expected result' do
|
it 'returns the expected result' do
|
||||||
@expected = [WPScan::Vulnerability.new(
|
@expected = [WPScan::Vulnerability.new(
|
||||||
'WP 3.8 - Vuln 1',
|
'WP 3.8 - Vuln 1',
|
||||||
{ url: %w[url-4], osvdb: %w[11], wpvulndb: '3' },
|
{ url: %w[url-4], wpvulndb: '3' },
|
||||||
'AUTHBYPASS'
|
'AUTHBYPASS'
|
||||||
)]
|
)]
|
||||||
end
|
end
|
||||||
@@ -67,6 +86,7 @@ describe WPScan::Model::WpVersion do
|
|||||||
|
|
||||||
context 'when multiple vulns' do
|
context 'when multiple vulns' do
|
||||||
let(:number) { '3.8.1' }
|
let(:number) { '3.8.1' }
|
||||||
|
let(:db_data) { vuln_api_data_for('wordpresses/381') }
|
||||||
|
|
||||||
it 'returns the expected results' do
|
it 'returns the expected results' do
|
||||||
@expected = [
|
@expected = [
|
||||||
@@ -77,7 +97,7 @@ describe WPScan::Model::WpVersion do
|
|||||||
),
|
),
|
||||||
WPScan::Vulnerability.new(
|
WPScan::Vulnerability.new(
|
||||||
'WP 3.8.1 - Vuln 2',
|
'WP 3.8.1 - Vuln 2',
|
||||||
{ url: %w[url-2 url-3], osvdb: %w[10], cve: %w[2014-0166], wpvulndb: '2' },
|
{ url: %w[url-2 url-3], cve: %w[2014-0166], wpvulndb: '2' },
|
||||||
nil,
|
nil,
|
||||||
'3.8.2'
|
'3.8.2'
|
||||||
)
|
)
|
||||||
@@ -87,27 +107,30 @@ describe WPScan::Model::WpVersion do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#release_date' do
|
describe '#metadata, #release_date, #status' do
|
||||||
subject(:version) { described_class.new('3.8.1') }
|
subject(:version) { described_class.new('3.8.1') }
|
||||||
|
|
||||||
its(:release_date) { should eql '2014-01-23' }
|
before { allow(version).to receive(:db_data).and_return(db_data) }
|
||||||
|
|
||||||
context 'when the version is not in the DB' do
|
context 'when no db_data' do
|
||||||
subject(:version) { described_class.new('3.8.2') }
|
let(:db_data) { {} }
|
||||||
|
|
||||||
its(:release_date) { should eql 'Unknown' }
|
its(:release_date) { should eql '2014-01-23' }
|
||||||
|
its(:status) { should eql 'outdated' }
|
||||||
|
|
||||||
|
context 'when the version is not in the metadata' do
|
||||||
|
subject(:version) { described_class.new('3.8.2') }
|
||||||
|
|
||||||
|
its(:release_date) { should eql 'Unknown' }
|
||||||
|
its(:status) { should eql 'Unknown' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
describe '#status' do
|
context 'when db_data' do
|
||||||
subject(:version) { described_class.new('3.8.1') }
|
let(:db_data) { vuln_api_data_for('wordpresses/381') }
|
||||||
|
|
||||||
its(:status) { should eql 'outdated' }
|
its(:release_date) { should eql '2014-01-23-via-api' }
|
||||||
|
its(:status) { should eql 'outdated-via-api' }
|
||||||
context 'when the version is not in the DB' do
|
|
||||||
subject(:version) { described_class.new('3.8.2') }
|
|
||||||
|
|
||||||
its(:release_date) { should eql 'Unknown' }
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ describe 'App::Views' do
|
|||||||
# in the expected output.
|
# in the expected output.
|
||||||
%i[JSON CliNoColour].each do |formatter|
|
%i[JSON CliNoColour].each do |formatter|
|
||||||
context "when #{formatter}" do
|
context "when #{formatter}" do
|
||||||
|
it_behaves_like 'App::Views::VulnApi'
|
||||||
it_behaves_like 'App::Views::WpVersion'
|
it_behaves_like 'App::Views::WpVersion'
|
||||||
it_behaves_like 'App::Views::MainTheme'
|
it_behaves_like 'App::Views::MainTheme'
|
||||||
it_behaves_like 'App::Views::Enumeration'
|
it_behaves_like 'App::Views::Enumeration'
|
||||||
|
|||||||
11194
spec/fixtures/db/dynamic_finders.yml
vendored
11194
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
spec/fixtures/db/plugins.json
vendored
25
spec/fixtures/db/plugins.json
vendored
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"no-vulns-popular": {
|
|
||||||
"vulnerabilities": [],
|
|
||||||
"popular": true,
|
|
||||||
"latest_version": "2.0",
|
|
||||||
"last_updated": "2015-05-16T00:00:00.000Z"
|
|
||||||
},
|
|
||||||
"vulnerable-not-popular": {
|
|
||||||
"latest_version": null,
|
|
||||||
"last_updated": null,
|
|
||||||
"popular": false,
|
|
||||||
"vulnerabilities" : [
|
|
||||||
{
|
|
||||||
"title" : "First Vuln",
|
|
||||||
"fixed_in" : "6.3.10",
|
|
||||||
"id" : 1,
|
|
||||||
"vuln_type": "LFI"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "No Fixed In",
|
|
||||||
"id": 2
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1
spec/fixtures/db/sponsor.txt
vendored
Normal file
1
spec/fixtures/db/sponsor.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Sponsored By Kittens
|
||||||
48
spec/fixtures/db/themes.json
vendored
48
spec/fixtures/db/themes.json
vendored
@@ -1,48 +0,0 @@
|
|||||||
{
|
|
||||||
"no-vulns-popular": {
|
|
||||||
"popular": true,
|
|
||||||
"latest_version": "2.0",
|
|
||||||
"last_updated": "2015-05-16T00:00:00.000Z",
|
|
||||||
"vulnerabilities": []
|
|
||||||
},
|
|
||||||
"dignitas-themes": {
|
|
||||||
"popular": true,
|
|
||||||
"latest_version": null,
|
|
||||||
"last_updated": null,
|
|
||||||
"vulnerabilities" : [
|
|
||||||
{
|
|
||||||
"created_at" : "2015-03-05T19:25:59.000Z",
|
|
||||||
"updated_at" : "2015-03-05T19:37:47.000Z",
|
|
||||||
"references": {
|
|
||||||
"url" : [
|
|
||||||
"http://research.evex.pw/?vuln=6",
|
|
||||||
"http://packetstormsecurity.com/files/130652/"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"title" : "Dignitas 1.1.9 - Privilage Escalation",
|
|
||||||
"id" : 7825,
|
|
||||||
"vuln_type" : "AUTHBYPASS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"yaaburnee-themes": {
|
|
||||||
"popular": false,
|
|
||||||
"latest_version": null,
|
|
||||||
"last_updated": null,
|
|
||||||
"vulnerabilities" : [
|
|
||||||
{
|
|
||||||
"created_at" : "2015-03-05T19:25:44.000Z",
|
|
||||||
"updated_at" : "2015-03-05T19:41:14.000Z",
|
|
||||||
"references": {
|
|
||||||
"url" : [
|
|
||||||
"http://research.evex.pw/?vuln=6",
|
|
||||||
"http://packetstormsecurity.com/files/130652/"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"title" : "Ya'aburnee 1.0.7 - Privilage Escalation",
|
|
||||||
"id" : 7824,
|
|
||||||
"vuln_type" : "AUTHBYPASS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
6
spec/fixtures/db/vuln_api/plugins/no-vulns-popular.json
vendored
Normal file
6
spec/fixtures/db/vuln_api/plugins/no-vulns-popular.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"vulnerabilities": [],
|
||||||
|
"popular": true,
|
||||||
|
"latest_version": "2.1",
|
||||||
|
"last_updated": "2015-05-16T00:00:00.000Z-via-api"
|
||||||
|
}
|
||||||
17
spec/fixtures/db/vuln_api/plugins/vulnerable-not-popular.json
vendored
Normal file
17
spec/fixtures/db/vuln_api/plugins/vulnerable-not-popular.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"latest_version": null,
|
||||||
|
"last_updated": null,
|
||||||
|
"popular": false,
|
||||||
|
"vulnerabilities" : [
|
||||||
|
{
|
||||||
|
"title" : "First Vuln \u003c= 6.3.10 - LFI",
|
||||||
|
"fixed_in" : "6.3.10",
|
||||||
|
"id" : 1,
|
||||||
|
"vuln_type": "LFI"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "No Fixed In",
|
||||||
|
"id": 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
22
spec/fixtures/db/vuln_api/themes/dignitas-themes.json
vendored
Normal file
22
spec/fixtures/db/vuln_api/themes/dignitas-themes.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"popular": true,
|
||||||
|
"latest_version": null,
|
||||||
|
"last_updated": null,
|
||||||
|
"vulnerabilities" : [
|
||||||
|
{
|
||||||
|
"created_at" : "2015-03-05T19:25:59.000Z",
|
||||||
|
"updated_at" : "2015-03-05T19:37:47.000Z",
|
||||||
|
"references": {
|
||||||
|
"url" : [
|
||||||
|
"http://research.evex.pw/?vuln=6"
|
||||||
|
],
|
||||||
|
"packetstorm": [
|
||||||
|
"130652"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"title" : "Dignitas 1.1.9 - Privilage Escalation",
|
||||||
|
"id" : 7825,
|
||||||
|
"vuln_type" : "AUTHBYPASS"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
6
spec/fixtures/db/vuln_api/themes/no-vulns-popular.json
vendored
Normal file
6
spec/fixtures/db/vuln_api/themes/no-vulns-popular.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"popular": true,
|
||||||
|
"latest_version": "2.2",
|
||||||
|
"last_updated": "2015-05-16T00:00:00.000Z-via-api",
|
||||||
|
"vulnerabilities": []
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user