wpscan 4.0.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +24 -0
- data/app/controllers/core.rb +6 -0
- data/app/controllers/enumeration.rb +4 -2
- data/app/controllers/vuln_api.rb +60 -3
- data/app/models/wp_item.rb +1 -1
- data/app/models/wp_version.rb +1 -1
- data/lib/opt_parse_validator/config_files_loader_merger.rb +2 -2
- data/lib/wpscan/db/updater.rb +57 -23
- data/lib/wpscan/db/vuln_api.rb +43 -1
- data/lib/wpscan/errors/http.rb +4 -4
- data/lib/wpscan/errors/scan.rb +2 -2
- data/lib/wpscan/errors/vuln_api.rb +24 -0
- data/lib/wpscan/scan.rb +3 -2
- data/lib/wpscan/version.rb +1 -1
- data/lib/wpscan/vulnerability.rb +26 -1
- data/lib/wpscan/web_site.rb +4 -4
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c6215ce3d361d0754721dab264b279b43c0064cb2621a14720db0e15167bbc52
|
|
4
|
+
data.tar.gz: 6c092df8d32bb9f5e0a750c2801fb7cd086c6f258f404023581dc61c194fb962
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b74ca0ee959b570d23d23d51936f121e31a16ef73ec896c5b1f6e97f503d6000c129cb3655fac0b6bca8072f7047c2bfad46711b77fb16f4f4cbdd9069a10311
|
|
7
|
+
data.tar.gz: 3067ba3dc843c74014b1bbda40ca2be6549fc17924aa64456128a667f91d20957ef86c1d79b430767fc2cb9a4a4b1b9fbb3bfd1d3f92c5edf94bb6c993ae8b23
|
data/README.md
CHANGED
|
@@ -144,6 +144,8 @@ The WPScan CLI tool uses the [WordPress Vulnerability Database API](https://wpsc
|
|
|
144
144
|
|
|
145
145
|
Up to **25** API requests per day are given free of charge, that should be suitable to scan most WordPress websites at least once per day. When the daily 25 API requests are exhausted, WPScan will continue to work as normal but without any vulnerability data.
|
|
146
146
|
|
|
147
|
+
Enterprise customers can alternatively scan against a local database dump with no request limits — see [Enterprise Local Database (opt-in)](#enterprise-local-database-opt-in).
|
|
148
|
+
|
|
147
149
|
### How many API requests do you need?
|
|
148
150
|
|
|
149
151
|
- Our WordPress scanner makes one API request for the WordPress version, one request per installed plugin, and one request per the installed theme.
|
|
@@ -205,6 +207,28 @@ cli_options:
|
|
|
205
207
|
|
|
206
208
|
The API Token will be automatically loaded from the ENV variable `WPSCAN_API_TOKEN` if present. If the `--api-token` CLI option is also provided, the value from the CLI will be used.
|
|
207
209
|
|
|
210
|
+
## Enterprise Local Database (opt-in)
|
|
211
|
+
|
|
212
|
+
Enterprise customers can scan using a **local vulnerability database dump** instead of the online API, if they prefer to do so for privacy. Supply the enterprise token via the `--enterprise-db-token TOKEN` option:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
wpscan --url https://example.com --enterprise-db-token YOUR_ENTERPRISE_DB_TOKEN
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
When this option is used:
|
|
219
|
+
|
|
220
|
+
- During the normal database update, WPScan additionally downloads `plugins.json.gz`, `themes.json.gz` and `wordpresses.json.gz` from `enterprise-data.wpscan.org` (authenticated with the token). As with the rest of the database, these are only re-downloaded when they change.
|
|
221
|
+
- Plugin, theme and WordPress vulnerability data is then read from those local dumps; no requests are made to the WPScan API.
|
|
222
|
+
|
|
223
|
+
The token can also be provided via the `WPSCAN_ENTERPRISE_DB_TOKEN` environment variable, or the `enterprise_db_token` key in a configuration file (see [Load CLI options from file/s](#load-cli-options-from-files)):
|
|
224
|
+
|
|
225
|
+
```yml
|
|
226
|
+
cli_options:
|
|
227
|
+
enterprise_db_token: 'YOUR_ENTERPRISE_DB_TOKEN'
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
`--enterprise-db-token` and `--api-token` are mutually exclusive; supplying both (including via their respective environment variables) results in an error.
|
|
231
|
+
|
|
208
232
|
## API Service Status
|
|
209
233
|
|
|
210
234
|
If you experience connection issues with the WPScan API, you can check the service status at https://status.wpscan.com/. When API connection errors occur, WPScan will include a link to the status page in the error message.
|
data/app/controllers/core.rb
CHANGED
|
@@ -37,6 +37,12 @@ module WPScan
|
|
|
37
37
|
|
|
38
38
|
maybe_output_banner_help_and_version
|
|
39
39
|
|
|
40
|
+
# Fail fast on conflicting API tokens, before the DB update (which would download the
|
|
41
|
+
# enterprise DB dumps) and before any request is sent to the target. Done here rather
|
|
42
|
+
# than only in VulnApi#before_scan (which runs after this controller) so that the
|
|
43
|
+
# conflict also aborts DB-update-only runs (--update without --url).
|
|
44
|
+
VulnApi.validate_api_tokens!
|
|
45
|
+
|
|
40
46
|
update_db if update_db_required?
|
|
41
47
|
setup_cache
|
|
42
48
|
check_target_availability
|
|
@@ -14,8 +14,10 @@ module WPScan
|
|
|
14
14
|
# requirement for `vp`/`vt` is irrelevant in that case.
|
|
15
15
|
return if ParsedCli.wp_auth
|
|
16
16
|
|
|
17
|
-
# Check if vulnerable plugin/theme enumeration is requested without an API token
|
|
18
|
-
|
|
17
|
+
# Check if vulnerable plugin/theme enumeration is requested without an API token.
|
|
18
|
+
# The local enterprise DB dump (--enterprise-db-token) provides the same data without a token.
|
|
19
|
+
return unless (enum[:vulnerable_plugins] || enum[:vulnerable_themes]) &&
|
|
20
|
+
DB::VulnApi.token.nil? && !DB::VulnApi.local_db
|
|
19
21
|
|
|
20
22
|
raise Error::ApiTokenRequiredForVulnerableEnumeration
|
|
21
23
|
end
|
data/app/controllers/vuln_api.rb
CHANGED
|
@@ -4,7 +4,28 @@ module WPScan
|
|
|
4
4
|
module Controller
|
|
5
5
|
# Controller to handle the API token
|
|
6
6
|
class VulnApi < WPScan::Controller::Base
|
|
7
|
-
ENV_KEY
|
|
7
|
+
ENV_KEY = 'WPSCAN_API_TOKEN'
|
|
8
|
+
ENTERPRISE_ENV_KEY = 'WPSCAN_ENTERPRISE_DB_TOKEN'
|
|
9
|
+
|
|
10
|
+
# Class level so that Controller::Core (which runs first) can validate the tokens right after
|
|
11
|
+
# the CLI options have been parsed, aborting before anything else (DB update, requests to the
|
|
12
|
+
# target) is done.
|
|
13
|
+
class << self
|
|
14
|
+
# @return [ String, nil ] The enterprise DB token (CLI or ENV). Must match DB::Updater's resolution.
|
|
15
|
+
def enterprise_db_token
|
|
16
|
+
ParsedCli.enterprise_db_token || ENV.fetch(ENTERPRISE_ENV_KEY, nil)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @return [ String, nil ] The API token (CLI or ENV var)
|
|
20
|
+
def api_token
|
|
21
|
+
ParsedCli.api_token || ENV.fetch(ENV_KEY, nil)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @raise [ Error::ConflictingApiTokens ] When both the API and enterprise DB tokens are supplied
|
|
25
|
+
def validate_api_tokens!
|
|
26
|
+
raise Error::ConflictingApiTokens if enterprise_db_token && api_token
|
|
27
|
+
end
|
|
28
|
+
end
|
|
8
29
|
|
|
9
30
|
def cli_options
|
|
10
31
|
[
|
|
@@ -12,6 +33,14 @@ module WPScan
|
|
|
12
33
|
['--api-token TOKEN',
|
|
13
34
|
'The WPScan API Token to display vulnerability data, available at https://wpscan.com/profile']
|
|
14
35
|
),
|
|
36
|
+
OptString.new(
|
|
37
|
+
['--enterprise-db-token TOKEN',
|
|
38
|
+
'Use a local enterprise vulnerability database dump instead of the WPScan API. The ' \
|
|
39
|
+
'plugins/themes/wordpresses dumps are downloaded from enterprise-data.wpscan.org using ' \
|
|
40
|
+
'this token during the database update, then read locally (no per-finding API calls). Mutually ' \
|
|
41
|
+
"exclusive with --api-token. Can also be set via the #{ENTERPRISE_ENV_KEY} environment variable."],
|
|
42
|
+
{ advanced: true }
|
|
43
|
+
),
|
|
15
44
|
OptBoolean.new(
|
|
16
45
|
['--proxy-target-only',
|
|
17
46
|
'When used with --proxy, the proxy is only applied to requests made to the target, ' \
|
|
@@ -22,9 +51,15 @@ module WPScan
|
|
|
22
51
|
end
|
|
23
52
|
|
|
24
53
|
def before_scan
|
|
25
|
-
|
|
54
|
+
# Already done by Core#before_scan (before the DB update, to fail as early as possible),
|
|
55
|
+
# kept as a safety net in case this controller is used in a chain without Core.
|
|
56
|
+
self.class.validate_api_tokens!
|
|
57
|
+
|
|
58
|
+
return setup_enterprise_db if enterprise_db_token
|
|
26
59
|
|
|
27
|
-
|
|
60
|
+
return unless api_token
|
|
61
|
+
|
|
62
|
+
DB::VulnApi.token = api_token
|
|
28
63
|
|
|
29
64
|
api_status = DB::VulnApi.status
|
|
30
65
|
|
|
@@ -36,6 +71,28 @@ module WPScan
|
|
|
36
71
|
def after_scan
|
|
37
72
|
output('status', status: DB::VulnApi.status, api_requests: WPScan.api_requests)
|
|
38
73
|
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
# @return [ String, nil ] The enterprise DB token (CLI or ENV)
|
|
78
|
+
def enterprise_db_token
|
|
79
|
+
self.class.enterprise_db_token
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# @return [ String, nil ] The API token (CLI or ENV var)
|
|
83
|
+
def api_token
|
|
84
|
+
self.class.api_token
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Switches DB::VulnApi to local-dump mode and ensures the dumps are present. Core's DB
|
|
88
|
+
# update normally downloads them before this controller runs (VulnApi is chained after Core).
|
|
89
|
+
def setup_enterprise_db
|
|
90
|
+
DB::VulnApi.local_db = true
|
|
91
|
+
|
|
92
|
+
missing = DB::VulnApi::ENTERPRISE_DB_FILES.values.reject { |file| File.exist?(DB_DIR.join(file)) }
|
|
93
|
+
|
|
94
|
+
raise Error::MissingEnterpriseDatabaseFile, missing unless missing.empty?
|
|
95
|
+
end
|
|
39
96
|
end
|
|
40
97
|
end
|
|
41
98
|
end
|
data/app/models/wp_item.rb
CHANGED
|
@@ -39,7 +39,7 @@ module WPScan
|
|
|
39
39
|
|
|
40
40
|
@vulnerabilities = []
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
Vulnerability.sort_json(db_data['vulnerabilities']).each do |json_vuln|
|
|
43
43
|
vulnerability = Vulnerability.load_from_json(json_vuln)
|
|
44
44
|
vulnerability.detected_version = version || nil
|
|
45
45
|
@vulnerabilities << vulnerability if vulnerable_to?(vulnerability)
|
data/app/models/wp_version.rb
CHANGED
|
@@ -57,7 +57,7 @@ module WPScan
|
|
|
57
57
|
|
|
58
58
|
@vulnerabilities = []
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
Vulnerability.sort_json(db_data['vulnerabilities']).each do |json_vuln|
|
|
61
61
|
vulnerability = Vulnerability.load_from_json(json_vuln)
|
|
62
62
|
vulnerability.detected_version = self
|
|
63
63
|
@vulnerabilities << vulnerability
|
|
@@ -4,7 +4,7 @@ require_relative 'config_files_loader_merger/base'
|
|
|
4
4
|
require_relative 'config_files_loader_merger/json'
|
|
5
5
|
require_relative 'config_files_loader_merger/yml'
|
|
6
6
|
|
|
7
|
-
# :
|
|
7
|
+
# simplecov:disable
|
|
8
8
|
# @param [ String ] path The path of the file to load
|
|
9
9
|
def yaml_safe_load(path)
|
|
10
10
|
if Gem::Version.new(Psych::VERSION) >= Gem::Version.new('3.1.0.pre1') # Ruby 2.6
|
|
@@ -13,7 +13,7 @@ def yaml_safe_load(path)
|
|
|
13
13
|
YAML.safe_load_file(path, [Regexp]) || {}
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
|
-
# :
|
|
16
|
+
# simplecov:enable
|
|
17
17
|
|
|
18
18
|
module OptParseValidator
|
|
19
19
|
# Options Files container
|
data/lib/wpscan/db/updater.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module WPScan
|
|
4
4
|
module DB
|
|
5
5
|
# Class used to perform DB updates
|
|
6
|
-
# :
|
|
6
|
+
# simplecov:disable
|
|
7
7
|
class Updater
|
|
8
8
|
# /!\ Might want to also update the Enumeration#cli_options when some filenames are changed here
|
|
9
9
|
FILES = %w[
|
|
@@ -17,6 +17,13 @@ module WPScan
|
|
|
17
17
|
wordpresses.json plugins.json themes.json
|
|
18
18
|
].freeze
|
|
19
19
|
|
|
20
|
+
# Enterprise vulnerability DB dumps, downloaded (in addition to FILES) only when an
|
|
21
|
+
# --enterprise-db-token is supplied, from ENTERPRISE_HOST using the X-DB-JSON-AUTH header.
|
|
22
|
+
ENTERPRISE_FILES = %w[wordpresses.json.gz plugins.json.gz themes.json.gz].freeze
|
|
23
|
+
|
|
24
|
+
ENTERPRISE_HOST = 'enterprise-data.wpscan.org'
|
|
25
|
+
DEFAULT_HOST = 'data.wpscan.org'
|
|
26
|
+
|
|
20
27
|
attr_reader :repo_directory
|
|
21
28
|
|
|
22
29
|
def initialize(repo_directory)
|
|
@@ -43,6 +50,24 @@ module WPScan
|
|
|
43
50
|
end
|
|
44
51
|
end
|
|
45
52
|
|
|
53
|
+
# @return [ String, nil ] The enterprise DB token. Resolved exactly like Controller::VulnApi
|
|
54
|
+
# does; the two must stay in sync or the dumps won't be downloaded.
|
|
55
|
+
def enterprise_db_token
|
|
56
|
+
ParsedCli.enterprise_db_token || ENV.fetch('WPSCAN_ENTERPRISE_DB_TOKEN', nil)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @return [ Array<String> ] The files to keep in sync (plus the enterprise dumps when a token is set)
|
|
60
|
+
def files
|
|
61
|
+
return FILES + ENTERPRISE_FILES if enterprise_db_token
|
|
62
|
+
|
|
63
|
+
FILES
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# @return [ Boolean ] Whether the file is an enterprise dump (fetched from ENTERPRISE_HOST with auth)
|
|
67
|
+
def enterprise_file?(filename)
|
|
68
|
+
!enterprise_db_token.nil? && ENTERPRISE_FILES.include?(filename)
|
|
69
|
+
end
|
|
70
|
+
|
|
46
71
|
# @return [ Time, nil ]
|
|
47
72
|
def last_update
|
|
48
73
|
Time.parse(File.read(last_update_file))
|
|
@@ -64,7 +89,7 @@ module WPScan
|
|
|
64
89
|
|
|
65
90
|
# @return [ Boolean ]
|
|
66
91
|
def missing_files?
|
|
67
|
-
|
|
92
|
+
files.each do |file|
|
|
68
93
|
return true unless File.exist?(repo_directory.join(file))
|
|
69
94
|
end
|
|
70
95
|
false
|
|
@@ -72,35 +97,44 @@ module WPScan
|
|
|
72
97
|
|
|
73
98
|
# @return [ Hash ] The params for Typhoeus::Request
|
|
74
99
|
# @note Those params can't be overriden by CLI options
|
|
75
|
-
def request_params
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
params
|
|
100
|
+
def request_params(filename = nil)
|
|
101
|
+
params = Browser.instance.default_request_params.merge(
|
|
102
|
+
timeout: 600,
|
|
103
|
+
connecttimeout: 300,
|
|
104
|
+
accept_encoding: 'gzip, deflate',
|
|
105
|
+
cache_ttl: 0,
|
|
106
|
+
headers: { 'User-Agent' => Browser.instance.default_user_agent }
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
if enterprise_file?(filename)
|
|
110
|
+
params[:headers]['X-DB-JSON-AUTH'] = enterprise_db_token
|
|
111
|
+
# The dumps are pre-gzipped static objects. If we advertise Accept-Encoding: gzip and the
|
|
112
|
+
# host responds with Content-Encoding: gzip, libcurl transparently decompresses at the
|
|
113
|
+
# transfer layer, so res.body would be plain JSON written into a *.json.gz file, breaking
|
|
114
|
+
# both Zlib::GzipReader and the SHA512 checksum (computed over the raw .gz).
|
|
115
|
+
params.delete(:accept_encoding)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
if ParsedCli.proxy_target_only
|
|
119
|
+
params.delete(:proxy)
|
|
120
|
+
params.delete(:proxyuserpwd)
|
|
91
121
|
end
|
|
122
|
+
|
|
123
|
+
params
|
|
92
124
|
end
|
|
93
125
|
|
|
94
126
|
# @return [ String ] The raw file URL associated with the given filename
|
|
95
127
|
def remote_file_url(filename)
|
|
96
|
-
|
|
128
|
+
host = enterprise_file?(filename) ? ENTERPRISE_HOST : DEFAULT_HOST
|
|
129
|
+
|
|
130
|
+
"https://#{host}/#{filename}"
|
|
97
131
|
end
|
|
98
132
|
|
|
99
133
|
# @return [ String ] The checksum of the associated remote filename
|
|
100
134
|
def remote_file_checksum(filename)
|
|
101
135
|
url = "#{remote_file_url(filename)}.sha512"
|
|
102
136
|
|
|
103
|
-
res = Typhoeus.get(url, request_params)
|
|
137
|
+
res = Typhoeus.get(url, request_params(filename))
|
|
104
138
|
raise Error::Download, res if res.timed_out? || res.code != 200
|
|
105
139
|
|
|
106
140
|
res.body.chomp
|
|
@@ -142,7 +176,7 @@ module WPScan
|
|
|
142
176
|
file_path = local_file_path(filename)
|
|
143
177
|
file_url = remote_file_url(filename)
|
|
144
178
|
|
|
145
|
-
res = Typhoeus.get(file_url, request_params)
|
|
179
|
+
res = Typhoeus.get(file_url, request_params(filename))
|
|
146
180
|
raise Error::Download, res if res.timed_out? || res.code != 200
|
|
147
181
|
|
|
148
182
|
File.binwrite(file_path, res.body)
|
|
@@ -154,7 +188,7 @@ module WPScan
|
|
|
154
188
|
def update
|
|
155
189
|
updated = []
|
|
156
190
|
|
|
157
|
-
|
|
191
|
+
files.each do |filename|
|
|
158
192
|
db_checksum = remote_file_checksum(filename)
|
|
159
193
|
|
|
160
194
|
# Checking if the file needs to be updated
|
|
@@ -179,5 +213,5 @@ module WPScan
|
|
|
179
213
|
end
|
|
180
214
|
end
|
|
181
215
|
end
|
|
182
|
-
# :
|
|
216
|
+
# simplecov:enable
|
|
183
217
|
end
|
data/lib/wpscan/db/vuln_api.rb
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'zlib'
|
|
4
|
+
|
|
3
5
|
module WPScan
|
|
4
6
|
module DB
|
|
5
7
|
# WPVulnDB API
|
|
6
8
|
class VulnApi
|
|
7
9
|
NON_ERROR_CODES = [200, 403].freeze
|
|
8
10
|
|
|
11
|
+
# Local (enterprise) vulnerability DB dumps, read instead of the API when local_db is set.
|
|
12
|
+
# Downloaded to DB_DIR by DB::Updater when --enterprise-db-token is supplied.
|
|
13
|
+
ENTERPRISE_DB_FILES = {
|
|
14
|
+
plugins: 'plugins.json.gz',
|
|
15
|
+
themes: 'themes.json.gz',
|
|
16
|
+
wordpresses: 'wordpresses.json.gz'
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
9
19
|
class << self
|
|
10
|
-
|
|
20
|
+
# local_db switches vuln lookups from the API to the local dumps above
|
|
21
|
+
attr_accessor :token, :local_db
|
|
11
22
|
end
|
|
12
23
|
|
|
13
24
|
# @return [ Addressable::URI ]
|
|
@@ -15,6 +26,28 @@ module WPScan
|
|
|
15
26
|
@uri ||= Addressable::URI.parse('https://wpscan.com/api/v4/')
|
|
16
27
|
end
|
|
17
28
|
|
|
29
|
+
# @param [ String ] filename The gzipped JSON dump located in DB_DIR
|
|
30
|
+
#
|
|
31
|
+
# @return [ Hash ] The parsed content of the dump
|
|
32
|
+
def self.load_db(filename)
|
|
33
|
+
JSON.parse(Zlib::GzipReader.open(DB_DIR.join(filename).to_s, &:read))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @return [ Hash ]
|
|
37
|
+
def self.plugins_db
|
|
38
|
+
@plugins_db ||= load_db(ENTERPRISE_DB_FILES[:plugins])
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [ Hash ]
|
|
42
|
+
def self.themes_db
|
|
43
|
+
@themes_db ||= load_db(ENTERPRISE_DB_FILES[:themes])
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @return [ Hash ]
|
|
47
|
+
def self.wordpresses_db
|
|
48
|
+
@wordpresses_db ||= load_db(ENTERPRISE_DB_FILES[:wordpresses])
|
|
49
|
+
end
|
|
50
|
+
|
|
18
51
|
# @param [ String ] path
|
|
19
52
|
# @param [ Hash ] params
|
|
20
53
|
#
|
|
@@ -52,21 +85,30 @@ module WPScan
|
|
|
52
85
|
|
|
53
86
|
# @return [ Hash ]
|
|
54
87
|
def self.plugin_data(slug)
|
|
88
|
+
return plugins_db&.dig(slug) || {} if local_db
|
|
89
|
+
|
|
55
90
|
get("plugins/#{slug}")&.dig(slug) || {}
|
|
56
91
|
end
|
|
57
92
|
|
|
58
93
|
# @return [ Hash ]
|
|
59
94
|
def self.theme_data(slug)
|
|
95
|
+
return themes_db&.dig(slug) || {} if local_db
|
|
96
|
+
|
|
60
97
|
get("themes/#{slug}")&.dig(slug) || {}
|
|
61
98
|
end
|
|
62
99
|
|
|
63
100
|
# @return [ Hash ]
|
|
64
101
|
def self.wordpress_data(version_number)
|
|
102
|
+
# The dump is keyed by the dotted version; only the API URL strips the dots.
|
|
103
|
+
return wordpresses_db&.dig(version_number) || {} if local_db
|
|
104
|
+
|
|
65
105
|
get("wordpresses/#{version_number.tr('.', '')}")&.dig(version_number) || {}
|
|
66
106
|
end
|
|
67
107
|
|
|
68
108
|
# @return [ Hash ]
|
|
69
109
|
def self.status
|
|
110
|
+
return { 'plan' => 'enterprise', 'requests_remaining' => 'Unlimited', 'success' => true } if local_db
|
|
111
|
+
|
|
70
112
|
json = get('status', params: { version: WPScan::VERSION }, cache_ttl: 0)
|
|
71
113
|
|
|
72
114
|
json['requests_remaining'] = 'Unlimited' if json['requests_remaining'] == -1
|
data/lib/wpscan/errors/http.rb
CHANGED
|
@@ -61,20 +61,20 @@ module WPScan
|
|
|
61
61
|
|
|
62
62
|
# HTTP Authentication Required Error
|
|
63
63
|
class HTTPAuthRequired < Standard
|
|
64
|
-
# :
|
|
64
|
+
# simplecov:disable
|
|
65
65
|
def to_s
|
|
66
66
|
'HTTP authentication required (or was invalid), please provide it with --http-auth'
|
|
67
67
|
end
|
|
68
|
-
# :
|
|
68
|
+
# simplecov:enable
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
# Proxy Authentication Required Error
|
|
72
72
|
class ProxyAuthRequired < Standard
|
|
73
|
-
# :
|
|
73
|
+
# simplecov:disable
|
|
74
74
|
def to_s
|
|
75
75
|
'Proxy authentication required (or was invalid), please provide it with --proxy-auth'
|
|
76
76
|
end
|
|
77
|
-
# :
|
|
77
|
+
# simplecov:enable
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
# Access Forbidden Error
|
data/lib/wpscan/errors/scan.rb
CHANGED
|
@@ -40,5 +40,29 @@ module WPScan
|
|
|
40
40
|
'Please check https://status.wpscan.com/ for service status.'
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
|
+
|
|
44
|
+
# Error raised when both --api-token and --enterprise-db-token are supplied
|
|
45
|
+
class ConflictingApiTokens < Standard
|
|
46
|
+
def to_s
|
|
47
|
+
'--api-token and --enterprise-db-token are mutually exclusive, please provide only one ' \
|
|
48
|
+
'(this also applies to the WPSCAN_API_TOKEN and WPSCAN_ENTERPRISE_DB_TOKEN environment variables).'
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Error raised when --enterprise-db-token is set but a local DB dump is missing
|
|
53
|
+
class MissingEnterpriseDatabaseFile < Standard
|
|
54
|
+
attr_reader :files
|
|
55
|
+
|
|
56
|
+
def initialize(files = [])
|
|
57
|
+
@files = Array(files)
|
|
58
|
+
super()
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def to_s
|
|
62
|
+
"Missing enterprise database dump(s): #{files.join(', ')}. " \
|
|
63
|
+
'Run without --no-update so they can be downloaded from enterprise-data.wpscan.org, ' \
|
|
64
|
+
'and check that your --enterprise-db-token is valid.'
|
|
65
|
+
end
|
|
66
|
+
end
|
|
43
67
|
end
|
|
44
68
|
end
|
data/lib/wpscan/scan.rb
CHANGED
|
@@ -27,6 +27,7 @@ module WPScan
|
|
|
27
27
|
# not secrets themselves, just references to files
|
|
28
28
|
sensitive_args = %w[
|
|
29
29
|
--api-token
|
|
30
|
+
--enterprise-db-token
|
|
30
31
|
--http-auth
|
|
31
32
|
--proxy-auth
|
|
32
33
|
--cookie-string
|
|
@@ -95,7 +96,7 @@ module WPScan
|
|
|
95
96
|
|
|
96
97
|
# Hook to be able to have an exit code returned
|
|
97
98
|
# depending on the findings / errors
|
|
98
|
-
# :
|
|
99
|
+
# simplecov:disable
|
|
99
100
|
def exit_hook
|
|
100
101
|
# Avoid registering this hook when the RSpec suite is running, otherwise it
|
|
101
102
|
# can override RSpec's exit status and hide failed builds.
|
|
@@ -109,7 +110,7 @@ module WPScan
|
|
|
109
110
|
exit(WPScan::ExitCode::OK)
|
|
110
111
|
end
|
|
111
112
|
end
|
|
112
|
-
# :
|
|
113
|
+
# simplecov:enable
|
|
113
114
|
|
|
114
115
|
def rspec_running?
|
|
115
116
|
defined?(RSpec)
|
data/lib/wpscan/version.rb
CHANGED
data/lib/wpscan/vulnerability.rb
CHANGED
|
@@ -56,13 +56,38 @@ module WPScan
|
|
|
56
56
|
json_data['title'],
|
|
57
57
|
references: references,
|
|
58
58
|
type: json_data['vuln_type'],
|
|
59
|
-
affected_versions: json_data
|
|
59
|
+
affected_versions: affected_versions_from(json_data),
|
|
60
60
|
cvss: json_data['cvss']&.symbolize_keys,
|
|
61
61
|
poc: json_data['poc'],
|
|
62
62
|
uuid: json_data['id'].to_s # The 'id' field IS the UUID in WPScan API
|
|
63
63
|
)
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
+
# Reads the :affected_versions ranges used by both the API and the DB dumps.
|
|
67
|
+
# Legacy flat fixed_in / introduced_in entries are wrapped into a single range
|
|
68
|
+
# so they still get version-filtered instead of matching every version.
|
|
69
|
+
#
|
|
70
|
+
# @param [ Hash ] json_data
|
|
71
|
+
# @return [ Array<Hash>, nil ]
|
|
72
|
+
def self.affected_versions_from(json_data)
|
|
73
|
+
return json_data['affected_versions'] if json_data['affected_versions']
|
|
74
|
+
return nil unless json_data.key?('fixed_in') || json_data.key?('introduced_in')
|
|
75
|
+
|
|
76
|
+
[{ 'fixed_in' => json_data['fixed_in'], 'introduced_in' => json_data['introduced_in'] }]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Orders entries oldest-first (created_at, updated_at, id) to match the API's
|
|
80
|
+
# order, so the API and the reverse-ordered DB dump report the same sequence.
|
|
81
|
+
# The trailing index keeps the sort stable when those fields are absent.
|
|
82
|
+
#
|
|
83
|
+
# @param [ Array<Hash> ] json_vulns Raw entries from the API or a DB dump
|
|
84
|
+
# @return [ Array<Hash> ]
|
|
85
|
+
def self.sort_json(json_vulns)
|
|
86
|
+
Array(json_vulns).each_with_index.sort_by do |vuln, index|
|
|
87
|
+
[vuln['created_at'].to_s, vuln['updated_at'].to_s, vuln['id'].to_s, index]
|
|
88
|
+
end.map(&:first)
|
|
89
|
+
end
|
|
90
|
+
|
|
66
91
|
# Whether this vulnerability covers a given version. True when no
|
|
67
92
|
# affected_versions are declared (advisory with no fix metadata) or when at
|
|
68
93
|
# least one backported range covers the version.
|
data/lib/wpscan/web_site.rb
CHANGED
|
@@ -45,11 +45,11 @@ module WPScan
|
|
|
45
45
|
# @return [ Typhoeus::Response ]
|
|
46
46
|
#
|
|
47
47
|
# As webmock does not support redirects mocking, coverage is ignored
|
|
48
|
-
# :
|
|
48
|
+
# simplecov:disable
|
|
49
49
|
def homepage_res
|
|
50
50
|
@homepage_res ||= WPScan::Browser.get_and_follow_location(url)
|
|
51
51
|
end
|
|
52
|
-
# :
|
|
52
|
+
# simplecov:enable
|
|
53
53
|
|
|
54
54
|
# @return [ String ]
|
|
55
55
|
def homepage_url
|
|
@@ -108,7 +108,7 @@ module WPScan
|
|
|
108
108
|
# @return [ String ] The redirection url or nil
|
|
109
109
|
#
|
|
110
110
|
# As webmock does not support redirects mocking, coverage is ignored
|
|
111
|
-
# :
|
|
111
|
+
# simplecov:disable
|
|
112
112
|
def redirection(url = nil)
|
|
113
113
|
url ||= @uri.to_s
|
|
114
114
|
|
|
@@ -118,7 +118,7 @@ module WPScan
|
|
|
118
118
|
|
|
119
119
|
res.effective_url == url ? nil : res.effective_url
|
|
120
120
|
end
|
|
121
|
-
# :
|
|
121
|
+
# simplecov:enable
|
|
122
122
|
|
|
123
123
|
# @return [ Hash ] The Typhoeus params to use to perform head requests
|
|
124
124
|
def head_or_get_params
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: wpscan
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.0
|
|
4
|
+
version: 4.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- WPScanTeam
|
|
@@ -351,14 +351,14 @@ dependencies:
|
|
|
351
351
|
requirements:
|
|
352
352
|
- - "~>"
|
|
353
353
|
- !ruby/object:Gem::Version
|
|
354
|
-
version: 0.
|
|
354
|
+
version: 1.0.0
|
|
355
355
|
type: :development
|
|
356
356
|
prerelease: false
|
|
357
357
|
version_requirements: !ruby/object:Gem::Requirement
|
|
358
358
|
requirements:
|
|
359
359
|
- - "~>"
|
|
360
360
|
- !ruby/object:Gem::Version
|
|
361
|
-
version: 0.
|
|
361
|
+
version: 1.0.0
|
|
362
362
|
- !ruby/object:Gem::Dependency
|
|
363
363
|
name: simplecov-lcov
|
|
364
364
|
requirement: !ruby/object:Gem::Requirement
|