wpscan 3.5.2 → 3.5.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 248dcdf858b8942a71b8515687d143065c9d37acfb38fb194f84cc2df850559f
4
- data.tar.gz: db972ad0382352dba907c028c003635c045753bcf2b71f9da7759f70520761c7
3
+ metadata.gz: 9e44669757a1872794cbd7427372c477dbc9b44b1e59b3683175a6d5d365c749
4
+ data.tar.gz: 54aadf50c99206305fcfa920c2a6f150da4071089074b5a157aa28c92daede80
5
5
  SHA512:
6
- metadata.gz: a41f8a0b3526c48ad366004244643e40dc37e5c8ad510a9ac40c848ca9826d47e6e59e5332e29e004c9059b10314ead5751dc0318db72a8a6bcd7ad19c18254b
7
- data.tar.gz: 79b2f651165ed7189c94b1298a99ea7409a87f03eda56014dd418f2c9f5ef51c9d0d6fc0ac14c849a369b7f4a9bebc682364e389a7ba4fa1ff9b0763e1e6d26d
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
- OptString.new(['--wp-plugins-dir DIR'])
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.in_scope_urls(target.homepage_res) do |url|
13
- next unless Addressable::URI.parse(url).path =~ pattern
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.in_scope_urls(res, '//style/@src|//link/@href') do |url|
24
- next unless Addressable::URI.parse(url).path =~ %r{/themes/([^\/]+)/style.css\z}i
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], url, opts)
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, 301, 500].freeze
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: [200, 401, 403, 500])) do |_res, slug|
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, 301, 500].freeze
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: [200, 401, 403, 500])) do |_res, slug|
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.in_scope_urls(res, '//link/@href|//a/@href') do |url|
87
- username = username_from_author_url(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.in_scope_urls(res, '//a/@href') do |url, node|
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.in_scope_urls(target.homepage_res, "//link[@rel='https://api.w.org/']/@href").each do |url, _tag|
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.in_scope_urls(target.homepage_res) do |url|
16
- next unless url =~ item_attribute_pattern(type)
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
@@ -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, :in_scope_urls, :head_or_get_params, to: :blog
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
- 'Scanning *.wordpress.com hosted blogs is not supported.'
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.in_scope_urls(response, xpath) do |url, _tag|
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] << url
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
- escaped_url = Regexp.escape(url).gsub(/https?/i, 'https?')
20
- pattern = %r{#{escaped_url}([\w\s\-\/]+)\/(?:themes|plugins|uploads|cache)\/}i
20
+ # scope_url_pattern is from CMSScanner::Target
21
+ pattern = %r{#{scope_url_pattern}([\w\s\-/]+)\\?/(?:themes|plugins|uploads|cache)\\?/}i
21
22
 
22
- in_scope_urls(homepage_res) do |url|
23
- return @content_dir = Regexp.last_match[1] if url.match(pattern)
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
- xpath_pattern_from_page('//script[not(@src)]', pattern, homepage_res) do |match|
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
- escaped_url = Regexp.escape(url).gsub(/https?/i, 'https?')
95
- pattern = %r{#{escaped_url}(.+?)\/(?:xmlrpc\.php|wp\-includes\/)}i
105
+ # url_pattern is from CMSScanner::Target
106
+ pattern = %r{#{url_pattern}(.+?)/(?:xmlrpc\.php|wp\-includes/)}i
96
107
 
97
- in_scope_urls(homepage_res) do |url|
98
- return @sub_dir = Regexp.last_match[1] if url.match(pattern)
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
- in_scope_urls(homepage_res) do |url|
28
- return true if Addressable::URI.parse(url).path.match(WORDPRESS_PATTERN)
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
- in_scope_urls(Browser.get_and_follow_location(url(path))).each do |url|
40
- return true if Addressable::URI.parse(url).path.match(WORDPRESS_PATTERN)
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) ? true : false
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
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Version
4
4
  module WPScan
5
- VERSION = '3.5.2'
5
+ VERSION = '3.5.3'
6
6
  end
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.2
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-08 00:00:00.000000000 Z
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.44.1
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.44.1
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.2.0
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.2.0
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.1
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.1
124
+ version: 0.67.2
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: simplecov
127
127
  requirement: !ruby/object:Gem::Requirement