wpscan 3.5.2 → 3.5.3
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/controllers/custom_directories.rb +5 -3
- data/app/finders/interesting_findings/mu_plugins.rb +2 -2
- data/app/finders/main_theme/css_style.rb +3 -3
- data/app/finders/plugins/known_locations.rb +2 -2
- data/app/finders/themes/known_locations.rb +2 -2
- data/app/finders/users/author_id_brute_forcing.rb +2 -2
- data/app/finders/users/author_posts.rb +1 -3
- data/app/finders/users/wp_json_api.rb +1 -3
- data/app/finders/wp_items/urls_in_homepage.rb +2 -2
- data/app/models/wp_item.rb +1 -1
- data/lib/wpscan/errors/wordpress.rb +3 -2
- data/lib/wpscan/finders/dynamic_finder/version/query_parameter.rb +2 -4
- data/lib/wpscan/target/platform/wordpress/custom_directories.rb +22 -11
- data/lib/wpscan/target/platform/wordpress.rb +16 -5
- data/lib/wpscan/version.rb +1 -1
- metadata +8 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9e44669757a1872794cbd7427372c477dbc9b44b1e59b3683175a6d5d365c749
|
4
|
+
data.tar.gz: 54aadf50c99206305fcfa920c2a6f150da4071089074b5a157aa28c92daede80
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9c7b244ffd96125433c9aac5a2b2091241265e19f7bd0d15756615165deca18d72ea159374aa094b8cdafde83a416102b559fabae90ce49377c7093151f8e54a
|
7
|
+
data.tar.gz: c0b6f59ec952d80e7b566d9e4d91a7b6cdee8b7e08f59e76cc4376d79c709f981308d63c4fe13ccfb458fe042c1d4fd71c2c93d5ee95422fc58268eb2bcccef3
|
@@ -7,8 +7,10 @@ module WPScan
|
|
7
7
|
class CustomDirectories < CMSScanner::Controller::Base
|
8
8
|
def cli_options
|
9
9
|
[
|
10
|
-
OptString.new(['--wp-content-dir DIR'
|
11
|
-
|
10
|
+
OptString.new(['--wp-content-dir DIR',
|
11
|
+
'The wp-content directory if custom or not detected, such as "wp-content"']),
|
12
|
+
OptString.new(['--wp-plugins-dir DIR',
|
13
|
+
'The plugins directory if custom or not detected, such as "wp-content/plugins"'])
|
12
14
|
]
|
13
15
|
end
|
14
16
|
|
@@ -16,7 +18,7 @@ module WPScan
|
|
16
18
|
target.content_dir = ParsedCli.wp_content_dir if ParsedCli.wp_content_dir
|
17
19
|
target.plugins_dir = ParsedCli.wp_plugins_dir if ParsedCli.wp_plugins_dir
|
18
20
|
|
19
|
-
return if target.content_dir
|
21
|
+
return if target.content_dir(ParsedCli.detection_mode)
|
20
22
|
|
21
23
|
raise Error::WpContentDirNotDetected
|
22
24
|
end
|
@@ -9,8 +9,8 @@ module WPScan
|
|
9
9
|
def passive(_opts = {})
|
10
10
|
pattern = %r{#{target.content_dir}/mu\-plugins/}i
|
11
11
|
|
12
|
-
target.
|
13
|
-
next unless
|
12
|
+
target.in_scope_uris(target.homepage_res) do |uri|
|
13
|
+
next unless uri.path =~ pattern
|
14
14
|
|
15
15
|
url = target.url('wp-content/mu-plugins/')
|
16
16
|
|
@@ -20,10 +20,10 @@ module WPScan
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def passive_from_css_href(res, opts)
|
23
|
-
target.
|
24
|
-
next unless
|
23
|
+
target.in_scope_uris(res, '//style/@src|//link/@href') do |uri|
|
24
|
+
next unless uri.path =~ %r{/themes/([^\/]+)/style.css\z}i
|
25
25
|
|
26
|
-
return create_theme(Regexp.last_match[1],
|
26
|
+
return create_theme(Regexp.last_match[1], uri.to_s, opts)
|
27
27
|
end
|
28
28
|
nil
|
29
29
|
end
|
@@ -9,7 +9,7 @@ module WPScan
|
|
9
9
|
|
10
10
|
# @return [ Array<Integer> ]
|
11
11
|
def valid_response_codes
|
12
|
-
@valid_response_codes ||= [200, 401, 403,
|
12
|
+
@valid_response_codes ||= [200, 401, 403, 500].freeze
|
13
13
|
end
|
14
14
|
|
15
15
|
# @param [ Hash ] opts
|
@@ -19,7 +19,7 @@ module WPScan
|
|
19
19
|
def aggressive(opts = {})
|
20
20
|
found = []
|
21
21
|
|
22
|
-
enumerate(target_urls(opts), opts.merge(check_full_response:
|
22
|
+
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |_res, slug|
|
23
23
|
found << Model::Plugin.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
24
24
|
end
|
25
25
|
|
@@ -9,7 +9,7 @@ module WPScan
|
|
9
9
|
|
10
10
|
# @return [ Array<Integer> ]
|
11
11
|
def valid_response_codes
|
12
|
-
@valid_response_codes ||= [200, 401, 403,
|
12
|
+
@valid_response_codes ||= [200, 401, 403, 500].freeze
|
13
13
|
end
|
14
14
|
|
15
15
|
# @param [ Hash ] opts
|
@@ -19,7 +19,7 @@ module WPScan
|
|
19
19
|
def aggressive(opts = {})
|
20
20
|
found = []
|
21
21
|
|
22
|
-
enumerate(target_urls(opts), opts.merge(check_full_response:
|
22
|
+
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |_res, slug|
|
23
23
|
found << Model::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
24
24
|
end
|
25
25
|
|
@@ -83,8 +83,8 @@ module WPScan
|
|
83
83
|
# @return [ String, nil ] The username found
|
84
84
|
def username_from_response(res)
|
85
85
|
# Permalink enabled
|
86
|
-
target.
|
87
|
-
username = username_from_author_url(
|
86
|
+
target.in_scope_uris(res, '//link/@href|//a/@href') do |uri|
|
87
|
+
username = username_from_author_url(uri.to_s)
|
88
88
|
return username if username
|
89
89
|
end
|
90
90
|
|
@@ -45,9 +45,7 @@ module WPScan
|
|
45
45
|
def potential_usernames(res)
|
46
46
|
usernames = []
|
47
47
|
|
48
|
-
target.
|
49
|
-
uri = Addressable::URI.parse(url)
|
50
|
-
|
48
|
+
target.in_scope_uris(res, '//a/@href') do |uri, node|
|
51
49
|
if uri.path =~ %r{/author/([^/\b]+)/?\z}i
|
52
50
|
usernames << [Regexp.last_match[1], 'Author Pattern', 100]
|
53
51
|
elsif /author=[0-9]+/.match?(uri.query)
|
@@ -57,9 +57,7 @@ module WPScan
|
|
57
57
|
def api_url
|
58
58
|
return @api_url if @api_url
|
59
59
|
|
60
|
-
target.
|
61
|
-
uri = Addressable::URI.parse(url.strip)
|
62
|
-
|
60
|
+
target.in_scope_uris(target.homepage_res, "//link[@rel='https://api.w.org/']/@href").each do |uri|
|
63
61
|
return @api_url = uri.join('wp/v2/users/').to_s if uri.path.include?('wp-json')
|
64
62
|
end
|
65
63
|
|
@@ -12,8 +12,8 @@ module WPScan
|
|
12
12
|
def items_from_links(type, uniq = true)
|
13
13
|
found = []
|
14
14
|
|
15
|
-
target.
|
16
|
-
next unless
|
15
|
+
target.in_scope_uris(target.homepage_res) do |uri|
|
16
|
+
next unless uri.to_s =~ item_attribute_pattern(type)
|
17
17
|
|
18
18
|
found << Regexp.last_match[1]
|
19
19
|
end
|
data/app/models/wp_item.rb
CHANGED
@@ -13,7 +13,7 @@ module WPScan
|
|
13
13
|
|
14
14
|
attr_reader :uri, :slug, :detection_opts, :version_detection_opts, :blog, :path_from_blog, :db_data
|
15
15
|
|
16
|
-
delegate :homepage_res, :xpath_pattern_from_page, :
|
16
|
+
delegate :homepage_res, :xpath_pattern_from_page, :in_scope_uris, :head_or_get_params, to: :blog
|
17
17
|
|
18
18
|
# @param [ String ] slug The plugin/theme slug
|
19
19
|
# @param [ Target ] blog The targeted blog
|
@@ -5,7 +5,7 @@ module WPScan
|
|
5
5
|
# WordPress hosted (*.wordpress.com)
|
6
6
|
class WordPressHosted < Standard
|
7
7
|
def to_s
|
8
|
-
'
|
8
|
+
'The target appears to be hosted on WordPress.com. Scanning such site is not supported.'
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
@@ -25,7 +25,8 @@ module WPScan
|
|
25
25
|
|
26
26
|
class WpContentDirNotDetected < Standard
|
27
27
|
def to_s
|
28
|
-
'Unable to identify the wp-content dir, please supply it with --wp-content-dir'
|
28
|
+
'Unable to identify the wp-content dir, please supply it with --wp-content-dir,' \
|
29
|
+
' use the --scope option or make sure the --url value given is the correct one'
|
29
30
|
end
|
30
31
|
end
|
31
32
|
end
|
@@ -35,15 +35,13 @@ module WPScan
|
|
35
35
|
def scan_response(response)
|
36
36
|
found = {}
|
37
37
|
|
38
|
-
target.
|
39
|
-
uri = Addressable::URI.parse(url)
|
40
|
-
|
38
|
+
target.in_scope_uris(response, xpath) do |uri|
|
41
39
|
next unless uri.path =~ path_pattern && uri.query&.match(self.class::PATTERN)
|
42
40
|
|
43
41
|
version = Regexp.last_match[:v].to_s
|
44
42
|
|
45
43
|
found[version] ||= []
|
46
|
-
found[version] <<
|
44
|
+
found[version] << uri.to_s
|
47
45
|
end
|
48
46
|
|
49
47
|
found
|
@@ -13,24 +13,36 @@ module WPScan
|
|
13
13
|
@plugins_dir = dir.chomp('/')
|
14
14
|
end
|
15
15
|
|
16
|
+
# @param [ Symbol ] detection_mode
|
16
17
|
# @return [ String ] The wp-content directory
|
17
|
-
def content_dir
|
18
|
+
def content_dir(detection_mode = :mixed)
|
18
19
|
unless @content_dir
|
19
|
-
|
20
|
-
pattern
|
20
|
+
# scope_url_pattern is from CMSScanner::Target
|
21
|
+
pattern = %r{#{scope_url_pattern}([\w\s\-/]+)\\?/(?:themes|plugins|uploads|cache)\\?/}i
|
21
22
|
|
22
|
-
|
23
|
-
return @content_dir = Regexp.last_match[1] if
|
23
|
+
in_scope_uris(homepage_res) do |uri|
|
24
|
+
return @content_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
|
24
25
|
end
|
25
26
|
|
26
|
-
|
27
|
+
# Checks for the pattern in raw JS code, as well as @content attributes of meta tags
|
28
|
+
xpath_pattern_from_page('//script[not(@src)]|//meta/@content', pattern, homepage_res) do |match|
|
27
29
|
return @content_dir = match[1]
|
28
30
|
end
|
31
|
+
|
32
|
+
unless detection_mode == :passive
|
33
|
+
return @content_dir = 'wp-content' if default_content_dir_exists?
|
34
|
+
end
|
29
35
|
end
|
30
36
|
|
31
37
|
@content_dir
|
32
38
|
end
|
33
39
|
|
40
|
+
def default_content_dir_exists?
|
41
|
+
# url('wp-content') can't be used here as the folder has not yet been identified
|
42
|
+
# and the method would try to replace it by nil which would raise an error
|
43
|
+
[200, 401, 403].include?(Browser.forge_request(uri.join('wp-content/').to_s, head_or_get_params).run.code)
|
44
|
+
end
|
45
|
+
|
34
46
|
# @return [ Addressable::URI ]
|
35
47
|
def content_uri
|
36
48
|
uri.join("#{content_dir}/")
|
@@ -85,17 +97,16 @@ module WPScan
|
|
85
97
|
themes_uri.join("#{URI.encode(slug)}/").to_s
|
86
98
|
end
|
87
99
|
|
88
|
-
# TODO: Factorise the code and the content_dir one ?
|
89
100
|
# @return [ String, False ] String of the sub_dir found, false otherwise
|
90
101
|
# @note: nil can not be returned here, otherwise if there is no sub_dir
|
91
102
|
# the check would be done each time
|
92
103
|
def sub_dir
|
93
104
|
unless @sub_dir
|
94
|
-
|
95
|
-
pattern
|
105
|
+
# url_pattern is from CMSScanner::Target
|
106
|
+
pattern = %r{#{url_pattern}(.+?)/(?:xmlrpc\.php|wp\-includes/)}i
|
96
107
|
|
97
|
-
|
98
|
-
return @sub_dir = Regexp.last_match[1] if
|
108
|
+
in_scope_uris(homepage_res) do |uri|
|
109
|
+
return @sub_dir = Regexp.last_match[1] if uri.to_s.match(pattern)
|
99
110
|
end
|
100
111
|
|
101
112
|
@sub_dir = false
|
@@ -24,8 +24,8 @@ module WPScan
|
|
24
24
|
#
|
25
25
|
# @return [ Boolean ]
|
26
26
|
def wordpress?(detection_mode)
|
27
|
-
|
28
|
-
return true if
|
27
|
+
in_scope_uris(homepage_res) do |uri|
|
28
|
+
return true if uri.path.match(WORDPRESS_PATTERN)
|
29
29
|
end
|
30
30
|
|
31
31
|
homepage_res.html.css('meta[name="generator"]').each do |node|
|
@@ -36,8 +36,8 @@ module WPScan
|
|
36
36
|
|
37
37
|
if %i[mixed aggressive].include?(detection_mode)
|
38
38
|
%w[wp-admin/install.php wp-login.php].each do |path|
|
39
|
-
|
40
|
-
return true if
|
39
|
+
in_scope_uris(Browser.get_and_follow_location(url(path))).each do |uri|
|
40
|
+
return true if uri.path.match(WORDPRESS_PATTERN)
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
@@ -78,8 +78,19 @@ module WPScan
|
|
78
78
|
multisite? ? url('wp-signup.php') : url('wp-login.php?action=register')
|
79
79
|
end
|
80
80
|
|
81
|
+
# @return [ Boolean ] Whether or not the target is hosted on wordpress.com
|
81
82
|
def wordpress_hosted?
|
82
|
-
/\.wordpress\.com$/i.match?(uri.host)
|
83
|
+
return true if /\.wordpress\.com$/i.match?(uri.host)
|
84
|
+
|
85
|
+
unless content_dir(:passive)
|
86
|
+
pattern = %r{https?://s\d\.wp\.com#{WORDPRESS_PATTERN}}i.freeze
|
87
|
+
|
88
|
+
uris_from_page(homepage_res) do |uri|
|
89
|
+
return true if uri.to_s.match?(pattern)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
false
|
83
94
|
end
|
84
95
|
|
85
96
|
# @param [ String ] username
|
data/lib/wpscan/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wpscan
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.5.
|
4
|
+
version: 3.5.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- WPScanTeam
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-04-
|
11
|
+
date: 2019-04-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cms_scanner
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.0
|
19
|
+
version: 0.5.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.0
|
26
|
+
version: 0.5.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -100,28 +100,28 @@ dependencies:
|
|
100
100
|
requirements:
|
101
101
|
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: 1.
|
103
|
+
version: 1.3.0
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: 1.
|
110
|
+
version: 1.3.0
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: rubocop
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
115
|
- - "~>"
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version: 0.67.
|
117
|
+
version: 0.67.2
|
118
118
|
type: :development
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
122
|
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
|
-
version: 0.67.
|
124
|
+
version: 0.67.2
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
126
|
name: simplecov
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|