wpscan 4.0.0 → 4.0.1
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/app/finders/wp_items/urls_in_page.rb +1 -1
- data/app/models/timthumb.rb +2 -2
- data/app/models/version.rb +1 -1
- data/app/models/wp_item.rb +7 -5
- data/app/models/wp_version.rb +3 -1
- data/lib/wpscan/db/vuln_api.rb +6 -2
- data/lib/wpscan/scan.rb +7 -4
- data/lib/wpscan/version.rb +1 -1
- data/lib/wpscan/vulnerability.rb +120 -14
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b6e5b02dd5de5e0624c1dca1b8bb3bad6f16ff008fe054957e80c3ce33807a70
|
|
4
|
+
data.tar.gz: fb7cec3911f60cabbf70ac8e34672e4a41fe68a64bd76e9d7b55f0f5e901d012
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 724a6be1ce5d754a9891781e7101bf5b504c056107d1a7dc4cb0bb107a03044ccfff73dd648f32e14bcc0d8299b8b7872e2cbdd8f813d928a2abd64402ed28f0
|
|
7
|
+
data.tar.gz: 9c31899c0110f312f496a934f585e661103a364cf535da47a49d9002594eb047b942715a299cf275fdc85c1143a725cb5ee9d3f3d86dcbe29a3e53f7a400c432
|
data/app/models/timthumb.rb
CHANGED
|
@@ -42,7 +42,7 @@ module WPScan
|
|
|
42
42
|
'Timthumb <= 1.32 Remote Code Execution',
|
|
43
43
|
references: { exploitdb: ['17602'] },
|
|
44
44
|
type: 'RCE',
|
|
45
|
-
fixed_in: '1.33'
|
|
45
|
+
affected_versions: [{ fixed_in: '1.33' }]
|
|
46
46
|
)
|
|
47
47
|
end
|
|
48
48
|
|
|
@@ -55,7 +55,7 @@ module WPScan
|
|
|
55
55
|
cve: '2014-4663'
|
|
56
56
|
},
|
|
57
57
|
type: 'RCE',
|
|
58
|
-
fixed_in: '2.8.14'
|
|
58
|
+
affected_versions: [{ fixed_in: '2.8.14' }]
|
|
59
59
|
)
|
|
60
60
|
end
|
|
61
61
|
|
data/app/models/version.rb
CHANGED
|
@@ -34,7 +34,7 @@ module WPScan
|
|
|
34
34
|
|
|
35
35
|
# @param [ Version, String ] other
|
|
36
36
|
def <=>(other)
|
|
37
|
-
other =
|
|
37
|
+
other = Version.new(other) unless other.is_a?(Version) # handle potential '.1' version
|
|
38
38
|
|
|
39
39
|
Gem::Version.new(number) <=> Gem::Version.new(other.number)
|
|
40
40
|
rescue ArgumentError
|
data/app/models/wp_item.rb
CHANGED
|
@@ -41,23 +41,25 @@ module WPScan
|
|
|
41
41
|
|
|
42
42
|
Array(db_data['vulnerabilities']).each do |json_vuln|
|
|
43
43
|
vulnerability = Vulnerability.load_from_json(json_vuln)
|
|
44
|
+
vulnerability.detected_version = version || nil
|
|
44
45
|
@vulnerabilities << vulnerability if vulnerable_to?(vulnerability)
|
|
45
46
|
end
|
|
46
47
|
|
|
47
48
|
@vulnerabilities
|
|
48
49
|
end
|
|
49
50
|
|
|
50
|
-
# Checks if the wp_item is vulnerable to a specific vulnerability
|
|
51
|
+
# Checks if the wp_item is vulnerable to a specific vulnerability.
|
|
52
|
+
# A vuln with multiple backported ranges matches when any single range
|
|
53
|
+
# covers the detected version.
|
|
51
54
|
#
|
|
52
55
|
# @param [ Vulnerability ] vuln Vulnerability to check the item against
|
|
53
56
|
#
|
|
54
57
|
# @return [ Boolean ]
|
|
55
58
|
def vulnerable_to?(vuln)
|
|
56
|
-
return
|
|
59
|
+
return true unless version
|
|
60
|
+
return true if vuln.nil?
|
|
57
61
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
version < vuln.fixed_in
|
|
62
|
+
vuln.affects?(version)
|
|
61
63
|
end
|
|
62
64
|
|
|
63
65
|
# @return [ String ]
|
data/app/models/wp_version.rb
CHANGED
|
@@ -58,7 +58,9 @@ module WPScan
|
|
|
58
58
|
@vulnerabilities = []
|
|
59
59
|
|
|
60
60
|
Array(db_data['vulnerabilities']).each do |json_vuln|
|
|
61
|
-
|
|
61
|
+
vulnerability = Vulnerability.load_from_json(json_vuln)
|
|
62
|
+
vulnerability.detected_version = self
|
|
63
|
+
@vulnerabilities << vulnerability
|
|
62
64
|
end
|
|
63
65
|
|
|
64
66
|
@vulnerabilities
|
data/lib/wpscan/db/vuln_api.rb
CHANGED
|
@@ -12,7 +12,7 @@ module WPScan
|
|
|
12
12
|
|
|
13
13
|
# @return [ Addressable::URI ]
|
|
14
14
|
def self.uri
|
|
15
|
-
@uri ||= Addressable::URI.parse('https://wpscan.com/api/
|
|
15
|
+
@uri ||= Addressable::URI.parse('https://wpscan.com/api/v4/')
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
# @param [ String ] path
|
|
@@ -21,7 +21,11 @@ module WPScan
|
|
|
21
21
|
# @return [ Hash ]
|
|
22
22
|
def self.get(path, params = {})
|
|
23
23
|
return {} unless token
|
|
24
|
-
|
|
24
|
+
# Guard against a slug or WordPress version equal to the literal
|
|
25
|
+
# string "latest": such a value would map to the
|
|
26
|
+
# /{plugins,themes,wordpresses,all}/latest list endpoints, which
|
|
27
|
+
# return a payload the per-entity parser below cannot handle.
|
|
28
|
+
return {} if path.end_with?('/latest')
|
|
25
29
|
|
|
26
30
|
# Typhoeus.get is used rather than Browser.get to avoid merging irrelevant params from the CLI
|
|
27
31
|
res = Typhoeus.get(uri.join(path), default_request_params.merge(params))
|
data/lib/wpscan/scan.rb
CHANGED
|
@@ -97,10 +97,9 @@ module WPScan
|
|
|
97
97
|
# depending on the findings / errors
|
|
98
98
|
# :nocov:
|
|
99
99
|
def exit_hook
|
|
100
|
-
# Avoid
|
|
101
|
-
#
|
|
102
|
-
|
|
103
|
-
return if defined?(RSpec)
|
|
100
|
+
# Avoid registering this hook when the RSpec suite is running, otherwise it
|
|
101
|
+
# can override RSpec's exit status and hide failed builds.
|
|
102
|
+
return if rspec_running?
|
|
104
103
|
|
|
105
104
|
at_exit do
|
|
106
105
|
exit(run_error_exit_code) if run_error
|
|
@@ -112,6 +111,10 @@ module WPScan
|
|
|
112
111
|
end
|
|
113
112
|
# :nocov:
|
|
114
113
|
|
|
114
|
+
def rspec_running?
|
|
115
|
+
defined?(RSpec)
|
|
116
|
+
end
|
|
117
|
+
|
|
115
118
|
# @return [ Integer ] The exit code related to the run_error
|
|
116
119
|
def run_error_exit_code
|
|
117
120
|
return WPScan::ExitCode::CLI_OPTION_ERROR if run_error.is_a?(OptParseValidator::Error) ||
|
data/lib/wpscan/version.rb
CHANGED
data/lib/wpscan/vulnerability.rb
CHANGED
|
@@ -5,7 +5,12 @@ module WPScan
|
|
|
5
5
|
class Vulnerability
|
|
6
6
|
include References
|
|
7
7
|
|
|
8
|
-
attr_reader :title, :type, :
|
|
8
|
+
attr_reader :title, :type, :affected_versions, :cvss, :poc, :uuid
|
|
9
|
+
|
|
10
|
+
# Version of the scanned item (plugin/theme/WordPress), used to resolve
|
|
11
|
+
# which backported range to surface as the scalar fixed_in / introduced_in.
|
|
12
|
+
# Left nil/false when the version could not be detected.
|
|
13
|
+
attr_accessor :detected_version
|
|
9
14
|
|
|
10
15
|
# @param [ String ] title
|
|
11
16
|
# @param [ Hash ] references
|
|
@@ -17,21 +22,21 @@ module WPScan
|
|
|
17
22
|
# @option references [ Array<String>, String ] :metasploit The related metasploit module(s)
|
|
18
23
|
# @option references [ Array<String> ] :youtube
|
|
19
24
|
# @param [ String ] type
|
|
20
|
-
# @param [
|
|
21
|
-
#
|
|
25
|
+
# @param [ Array<Hash> ] affected_versions One entry per backported fix range,
|
|
26
|
+
# each with :fixed_in and :introduced_in keys (either may be nil).
|
|
22
27
|
# @param [ HashSymbol ] cvss
|
|
23
28
|
# @option cvss [ String ] :score
|
|
24
29
|
# @option cvss [ String ] :vector
|
|
25
30
|
# rubocop:disable Metrics/ParameterLists
|
|
26
|
-
def initialize(title, references: {}, type: nil,
|
|
31
|
+
def initialize(title, references: {}, type: nil, affected_versions: nil, cvss: nil, poc: nil, uuid: nil)
|
|
27
32
|
# rubocop:enable Metrics/ParameterLists
|
|
28
|
-
@title
|
|
29
|
-
@type
|
|
30
|
-
@
|
|
31
|
-
@
|
|
32
|
-
@
|
|
33
|
-
@
|
|
34
|
-
@
|
|
33
|
+
@title = title
|
|
34
|
+
@type = type
|
|
35
|
+
@affected_versions = normalize_affected_versions(affected_versions)
|
|
36
|
+
@cvss = { score: cvss[:score], vector: cvss[:vector] } if cvss
|
|
37
|
+
@poc = poc
|
|
38
|
+
@uuid = uuid
|
|
39
|
+
@detected_version = nil
|
|
35
40
|
|
|
36
41
|
self.references = references
|
|
37
42
|
end
|
|
@@ -51,14 +56,78 @@ module WPScan
|
|
|
51
56
|
json_data['title'],
|
|
52
57
|
references: references,
|
|
53
58
|
type: json_data['vuln_type'],
|
|
54
|
-
|
|
55
|
-
introduced_in: json_data['introduced_in'],
|
|
59
|
+
affected_versions: json_data['affected_versions'],
|
|
56
60
|
cvss: json_data['cvss']&.symbolize_keys,
|
|
57
61
|
poc: json_data['poc'],
|
|
58
62
|
uuid: json_data['id'].to_s # The 'id' field IS the UUID in WPScan API
|
|
59
63
|
)
|
|
60
64
|
end
|
|
61
65
|
|
|
66
|
+
# Whether this vulnerability covers a given version. True when no
|
|
67
|
+
# affected_versions are declared (advisory with no fix metadata) or when at
|
|
68
|
+
# least one backported range covers the version.
|
|
69
|
+
#
|
|
70
|
+
# @param [ #< ] version
|
|
71
|
+
#
|
|
72
|
+
# @return [ Boolean ]
|
|
73
|
+
def affects?(version)
|
|
74
|
+
return true if affected_versions.empty?
|
|
75
|
+
|
|
76
|
+
affected_versions.any? { |range| self.class.range_covers?(range, version) }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Backward-compatible scalar: the fixed_in of the range relevant to the
|
|
80
|
+
# detected version (the branch the scanned site is actually on). nil when
|
|
81
|
+
# there is no fix metadata.
|
|
82
|
+
#
|
|
83
|
+
# @return [ String, nil ]
|
|
84
|
+
def fixed_in
|
|
85
|
+
relevant_range && relevant_range[:fixed_in]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Backward-compatible scalar counterpart of {#fixed_in}.
|
|
89
|
+
#
|
|
90
|
+
# @return [ String, nil ]
|
|
91
|
+
def introduced_in
|
|
92
|
+
relevant_range && relevant_range[:introduced_in]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# The single affected-version range whose fixed_in / introduced_in we
|
|
96
|
+
# surface in the output. Picks the range covering the detected version so a
|
|
97
|
+
# site on an older but still-supported branch is matched against the right
|
|
98
|
+
# fix. Falls back to the highest (most recent) range when the version is
|
|
99
|
+
# unknown or no range covers it, mirroring the pre-v4 single-value output.
|
|
100
|
+
#
|
|
101
|
+
# @return [ Hash{Symbol => String, nil}, nil ]
|
|
102
|
+
def relevant_range
|
|
103
|
+
return nil if affected_versions.empty?
|
|
104
|
+
|
|
105
|
+
if detected_version
|
|
106
|
+
covering = affected_versions.find { |range| self.class.range_covers?(range, detected_version) }
|
|
107
|
+
return covering if covering
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
highest_range
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Whether a single range covers the version. A range matches when
|
|
114
|
+
# introduced_in is missing or <= version AND fixed_in is missing or >
|
|
115
|
+
# version.
|
|
116
|
+
#
|
|
117
|
+
# @param [ Hash{Symbol => String, nil} ] range
|
|
118
|
+
# @param [ #< ] version
|
|
119
|
+
#
|
|
120
|
+
# @return [ Boolean ]
|
|
121
|
+
def self.range_covers?(range, version)
|
|
122
|
+
introduced = range[:introduced_in]
|
|
123
|
+
fixed = range[:fixed_in]
|
|
124
|
+
|
|
125
|
+
return false if introduced && !introduced.to_s.empty? && version < introduced
|
|
126
|
+
return true if fixed.nil? || fixed.to_s.empty?
|
|
127
|
+
|
|
128
|
+
version < fixed
|
|
129
|
+
end
|
|
130
|
+
|
|
62
131
|
# @param [ Vulnerability ] other
|
|
63
132
|
#
|
|
64
133
|
# @return [ Boolean ]
|
|
@@ -66,10 +135,47 @@ module WPScan
|
|
|
66
135
|
title == other.title &&
|
|
67
136
|
type == other.type &&
|
|
68
137
|
references == other.references &&
|
|
69
|
-
|
|
138
|
+
affected_versions == other.affected_versions &&
|
|
70
139
|
cvss == other.cvss &&
|
|
71
140
|
poc == other.poc &&
|
|
72
141
|
uuid == other.uuid
|
|
73
142
|
end
|
|
143
|
+
|
|
144
|
+
private
|
|
145
|
+
|
|
146
|
+
# The range with the greatest fixed_in. An unpatched range (no fixed_in)
|
|
147
|
+
# outranks every patched one. Used as the fallback for {#relevant_range}.
|
|
148
|
+
#
|
|
149
|
+
# @return [ Hash{Symbol => String, nil} ]
|
|
150
|
+
def highest_range
|
|
151
|
+
affected_versions.max_by { |range| version_key(range[:fixed_in]) }
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Comparable sort key for a fixed_in value. Returns a [tier, Gem::Version]
|
|
155
|
+
# tuple so a missing fixed_in (unpatched) always sorts highest, and patched
|
|
156
|
+
# ranges sort by their numeric version.
|
|
157
|
+
#
|
|
158
|
+
# @return [ Array ]
|
|
159
|
+
def version_key(fixed_in)
|
|
160
|
+
return [1, Gem::Version.new('0')] if fixed_in.nil? || fixed_in.to_s.empty?
|
|
161
|
+
|
|
162
|
+
[0, Gem::Version.new(fixed_in.to_s[/\A[\d.]+/] || '0')]
|
|
163
|
+
rescue ArgumentError
|
|
164
|
+
[0, Gem::Version.new('0')]
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Coerces the v4 affected_versions payload into an array of symbolised range
|
|
168
|
+
# hashes. Accepts either an array of hashes (with string or symbol keys) or
|
|
169
|
+
# nil; anything else is dropped so a malformed entry can't break parsing.
|
|
170
|
+
#
|
|
171
|
+
# @return [ Array<Hash{Symbol => String, nil}> ]
|
|
172
|
+
def normalize_affected_versions(value)
|
|
173
|
+
Array(value).filter_map do |range|
|
|
174
|
+
next unless range.is_a?(Hash)
|
|
175
|
+
|
|
176
|
+
range = range.transform_keys(&:to_sym)
|
|
177
|
+
{ fixed_in: range[:fixed_in], introduced_in: range[:introduced_in] }
|
|
178
|
+
end
|
|
179
|
+
end
|
|
74
180
|
end
|
|
75
181
|
end
|