wpscan 3.2.1 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +73 -70
  3. data/app/controllers.rb +1 -1
  4. data/app/controllers/enumeration.rb +1 -1
  5. data/app/controllers/enumeration/cli_options.rb +32 -15
  6. data/app/controllers/enumeration/enum_methods.rb +7 -0
  7. data/app/controllers/password_attack.rb +108 -0
  8. data/app/finders.rb +2 -0
  9. data/app/finders/config_backups/known_filenames.rb +1 -1
  10. data/app/finders/db_exports.rb +17 -0
  11. data/app/finders/db_exports/known_locations.rb +49 -0
  12. data/app/finders/interesting_findings/mu_plugins.rb +1 -0
  13. data/app/finders/main_theme/css_style.rb +1 -1
  14. data/app/finders/medias/attachment_brute_forcing.rb +1 -1
  15. data/app/finders/passwords.rb +3 -0
  16. data/app/finders/passwords/wp_login.rb +22 -0
  17. data/app/finders/passwords/xml_rpc.rb +22 -0
  18. data/app/finders/passwords/xml_rpc_multicall.rb +102 -0
  19. data/app/finders/users.rb +2 -0
  20. data/app/finders/users/author_id_brute_forcing.rb +3 -3
  21. data/app/finders/users/author_posts.rb +2 -2
  22. data/app/finders/users/login_error_messages.rb +1 -1
  23. data/app/finders/users/oembed_api.rb +4 -4
  24. data/app/finders/users/rss_generator.rb +38 -0
  25. data/app/finders/users/wp_json_api.rb +5 -5
  26. data/app/finders/wp_version/atom_generator.rb +1 -1
  27. data/app/finders/wp_version/rdf_generator.rb +1 -1
  28. data/app/finders/wp_version/rss_generator.rb +1 -1
  29. data/app/models.rb +1 -1
  30. data/app/models/db_export.rb +5 -0
  31. data/app/models/wp_item.rb +2 -0
  32. data/app/views/cli/core/banner.erb +1 -1
  33. data/app/views/cli/enumeration/db_exports.erb +11 -0
  34. data/app/views/cli/{brute_force → password_attack}/users.erb +0 -0
  35. data/app/views/json/enumeration/db_exports.erb +10 -0
  36. data/app/views/json/{brute_force → password_attack}/users.erb +1 -1
  37. data/bin/wpscan +1 -1
  38. data/lib/wpscan/browser.rb +1 -1
  39. data/lib/wpscan/db/dynamic_finders/plugin.rb +2 -2
  40. data/lib/wpscan/db/dynamic_finders/wordpress.rb +2 -2
  41. data/lib/wpscan/db/fingerprints.rb +1 -1
  42. data/lib/wpscan/db/updater.rb +4 -1
  43. data/lib/wpscan/finders/dynamic_finder/version/query_parameter.rb +2 -1
  44. data/lib/wpscan/finders/dynamic_finder/wp_item_version.rb +2 -1
  45. data/lib/wpscan/finders/dynamic_finder/wp_version.rb +5 -4
  46. data/lib/wpscan/target.rb +13 -0
  47. data/lib/wpscan/target/platform/wordpress/custom_directories.rb +1 -1
  48. data/lib/wpscan/version.rb +1 -1
  49. metadata +29 -22
  50. data/app/controllers/brute_force.rb +0 -116
  51. data/app/models/user.rb +0 -31
  52. data/app/views/cli/brute_force/error.erb +0 -1
  53. data/app/views/cli/brute_force/found.erb +0 -2
@@ -10,7 +10,7 @@ module WPScan
10
10
 
11
11
  # @return [ String ]
12
12
  def default_user_agent
13
- "WPScan v#{VERSION} (http://wpscan.org/)"
13
+ "WPScan v#{VERSION} (https://wpscan.org/)"
14
14
  end
15
15
  end
16
16
  end
@@ -28,7 +28,7 @@ module WPScan
28
28
  end
29
29
 
30
30
  fs.each do |finder_name, config|
31
- klass = config['class'] ? config['class'] : finder_name
31
+ klass = config['class'] || finder_name
32
32
 
33
33
  next unless klass.to_sym == finder_class
34
34
 
@@ -79,7 +79,7 @@ module WPScan
79
79
  mod = maybe_create_modudle(slug)
80
80
 
81
81
  finders.each do |finder_class, config|
82
- klass = config['class'] ? config['class'] : finder_class
82
+ klass = config['class'] || finder_class
83
83
 
84
84
  # Instead of raising exceptions, skip unallowed/already defined finders
85
85
  # So that, when new DF configs are put in the .yml
@@ -34,7 +34,7 @@ module WPScan
34
34
  end
35
35
 
36
36
  finders.each do |finder_name, config|
37
- klass = config['class'] ? config['class'] : finder_name
37
+ klass = config['class'] || finder_name
38
38
 
39
39
  next unless klass.to_sym == finder_class
40
40
 
@@ -51,7 +51,7 @@ module WPScan
51
51
 
52
52
  def self.create_versions_finders
53
53
  versions_finders_configs.each do |finder_class, config|
54
- klass = config['class'] ? config['class'] : finder_class
54
+ klass = config['class'] || finder_class
55
55
 
56
56
  # Instead of raising exceptions, skip unallowed/already defined finders
57
57
  # So that, when new DF configs are put in the .yml
@@ -33,7 +33,7 @@ module WPScan
33
33
 
34
34
  # @return [ String ]
35
35
  def self.wp_fingerprints_path
36
- @wp_unique_fingerprints_path ||= File.join(DB_DIR, 'wp_fingerprints.json')
36
+ @wp_fingerprints_path ||= File.join(DB_DIR, 'wp_fingerprints.json')
37
37
  end
38
38
 
39
39
  # @return [ Hash ]
@@ -7,7 +7,7 @@ module WPScan
7
7
  FILES = %w[
8
8
  plugins.json themes.json wordpresses.json
9
9
  timthumbs-v3.txt user-agents.txt config_backups.txt
10
- dynamic_finders.yml wp_fingerprints.json LICENSE
10
+ db_exports.txt dynamic_finders.yml wp_fingerprints.json LICENSE
11
11
  ].freeze
12
12
 
13
13
  OLD_FILES = %w[wordpress.db dynamic_finders_01.yml].freeze
@@ -82,6 +82,7 @@ module WPScan
82
82
 
83
83
  res = Browser.get(url, request_params)
84
84
  raise DownloadError, res if res.timed_out? || res.code != 200
85
+
85
86
  res.body.chomp
86
87
  end
87
88
 
@@ -99,11 +100,13 @@ module WPScan
99
100
 
100
101
  def create_backup(filename)
101
102
  return unless File.exist?(local_file_path(filename))
103
+
102
104
  FileUtils.cp(local_file_path(filename), backup_file_path(filename))
103
105
  end
104
106
 
105
107
  def restore_backup(filename)
106
108
  return unless File.exist?(backup_file_path(filename))
109
+
107
110
  FileUtils.cp(backup_file_path(filename), local_file_path(filename))
108
111
  end
109
112
 
@@ -37,6 +37,7 @@ module WPScan
37
37
  uri = Addressable::URI.parse(url)
38
38
 
39
39
  next unless uri.path =~ path_pattern && uri.query&.match(self.class::PATTERN)
40
+
40
41
  version = Regexp.last_match[:v].to_s
41
42
 
42
43
  found[version] ||= []
@@ -48,7 +49,7 @@ module WPScan
48
49
 
49
50
  # @return [ String ]
50
51
  def xpath
51
- @xpath ||= self.class::XPATH || '//link[@href]|//script[@src]'
52
+ @xpath ||= self.class::XPATH || '//link[@href]/@href|//script[@src]/@src'
52
53
  end
53
54
 
54
55
  # @return [ Regexp ]
@@ -30,7 +30,8 @@ module WPScan
30
30
 
31
31
  def xpath
32
32
  @xpath ||= self.class::XPATH ||
33
- "//link[contains(@href,'#{target.slug}')]|//script[contains(@src,'#{target.slug}')]"
33
+ "//link[contains(@href,'#{target.slug}')]/@href" \
34
+ "|//script[contains(@src,'#{target.slug}')]/@src"
34
35
  end
35
36
  end
36
37
 
@@ -37,13 +37,14 @@ module WPScan
37
37
 
38
38
  class WpItemQueryParameter < QueryParameter
39
39
  def xpath
40
- @xpath ||= self.class::XPATH ||
41
- "//link[contains(@href,'#{target.plugins_dir}') or contains(@href,'#{target.themes_dir}')]|" \
42
- "//script[contains(@src,'#{target.plugins_dir}') or contains(@src,'#{target.themes_dir}')]"
40
+ @xpath ||=
41
+ self.class::XPATH ||
42
+ "//link[contains(@href,'#{target.plugins_dir}') or contains(@href,'#{target.themes_dir}')]/@href" \
43
+ "|//script[contains(@src,'#{target.plugins_dir}') or contains(@src,'#{target.themes_dir}')]/@src"
43
44
  end
44
45
 
45
46
  def path_pattern
46
- @pattern ||= %r{
47
+ @path_pattern ||= %r{
47
48
  (?:#{Regexp.escape(target.plugins_dir)}|#{Regexp.escape(target.themes_dir)})/
48
49
  [^/]+/
49
50
  .*\.(?:css|js)\z
data/lib/wpscan/target.rb CHANGED
@@ -12,12 +12,18 @@ module WPScan
12
12
  end
13
13
 
14
14
  return true unless [*@config_backups].empty?
15
+ return true unless [*@db_exports].empty?
15
16
 
16
17
  [*@users].each { |u| return true if u.password }
17
18
 
18
19
  false
19
20
  end
20
21
 
22
+ # @return [ XMLRPC, nil ]
23
+ def xmlrpc
24
+ @xmlrpc ||= interesting_findings&.select { |f| f.is_a?(WPScan::XMLRPC) }&.first
25
+ end
26
+
21
27
  # @param [ Hash ] opts
22
28
  #
23
29
  # @return [ WpVersion, false ] The WpVersion found or false if not detected
@@ -64,6 +70,13 @@ module WPScan
64
70
  @config_backups ||= Finders::ConfigBackups::Base.find(self, opts)
65
71
  end
66
72
 
73
+ # @param [ Hash ] opts
74
+ #
75
+ # @return [ Array<DBExport> ]
76
+ def db_exports(opts = {})
77
+ @db_exports ||= Finders::DbExports::Base.find(self, opts)
78
+ end
79
+
67
80
  # @param [ Hash ] opts
68
81
  #
69
82
  # @return [ Array<Media> ]
@@ -15,7 +15,7 @@ module WPScan
15
15
  def content_dir
16
16
  unless @content_dir
17
17
  escaped_url = Regexp.escape(url).gsub(/https?/i, 'https?')
18
- pattern = %r{#{escaped_url}(.+?)\/(?:themes|plugins|uploads)\/}i
18
+ pattern = %r{#{escaped_url}(.+?)\/(?:themes|plugins|uploads|cache)\/}i
19
19
 
20
20
  in_scope_urls(homepage_res) do |url|
21
21
  return @content_dir = Regexp.last_match[1] if url.match(pattern)
@@ -1,4 +1,4 @@
1
1
  # Version
2
2
  module WPScan
3
- VERSION = '3.2.1'.freeze
3
+ VERSION = '3.3.0'.freeze
4
4
  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.2.1
4
+ version: 3.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - WPScanTeam
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-03-04 00:00:00.000000000 Z
11
+ date: 2018-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cms_scanner
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.0.39.1
19
+ version: 0.0.40
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.39.1
26
+ version: 0.0.40
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activesupport
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '5.1'
33
+ version: '5.2'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '5.1'
40
+ version: '5.2'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: yajl-ruby
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -86,28 +86,28 @@ dependencies:
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '12.0'
89
+ version: '12.3'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '12.0'
96
+ version: '12.3'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: rspec
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: 3.7.0
103
+ version: 3.8.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: 3.7.0
110
+ version: 3.8.0
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: rspec-its
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -128,42 +128,42 @@ dependencies:
128
128
  requirements:
129
129
  - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: 0.52.0
131
+ version: 0.59.2
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
- version: 0.52.0
138
+ version: 0.59.2
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: simplecov
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
143
  - - "~>"
144
144
  - !ruby/object:Gem::Version
145
- version: 0.14.0
145
+ version: 0.16.1
146
146
  type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
150
  - - "~>"
151
151
  - !ruby/object:Gem::Version
152
- version: 0.14.0
152
+ version: 0.16.1
153
153
  - !ruby/object:Gem::Dependency
154
154
  name: webmock
155
155
  requirement: !ruby/object:Gem::Requirement
156
156
  requirements:
157
157
  - - "~>"
158
158
  - !ruby/object:Gem::Version
159
- version: 3.3.0
159
+ version: 3.4.2
160
160
  type: :development
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
164
164
  - - "~>"
165
165
  - !ruby/object:Gem::Version
166
- version: 3.3.0
166
+ version: 3.4.2
167
167
  description: WPScan is a black box WordPress vulnerability scanner.
168
168
  email:
169
169
  - team@wpscan.org
@@ -177,17 +177,19 @@ files:
177
177
  - app/app.rb
178
178
  - app/controllers.rb
179
179
  - app/controllers/aliases.rb
180
- - app/controllers/brute_force.rb
181
180
  - app/controllers/core.rb
182
181
  - app/controllers/custom_directories.rb
183
182
  - app/controllers/enumeration.rb
184
183
  - app/controllers/enumeration/cli_options.rb
185
184
  - app/controllers/enumeration/enum_methods.rb
186
185
  - app/controllers/main_theme.rb
186
+ - app/controllers/password_attack.rb
187
187
  - app/controllers/wp_version.rb
188
188
  - app/finders.rb
189
189
  - app/finders/config_backups.rb
190
190
  - app/finders/config_backups/known_filenames.rb
191
+ - app/finders/db_exports.rb
192
+ - app/finders/db_exports/known_locations.rb
191
193
  - app/finders/interesting_findings.rb
192
194
  - app/finders/interesting_findings/backup_db.rb
193
195
  - app/finders/interesting_findings/debug_log.rb
@@ -207,6 +209,10 @@ files:
207
209
  - app/finders/main_theme/woo_framework_meta_generator.rb
208
210
  - app/finders/medias.rb
209
211
  - app/finders/medias/attachment_brute_forcing.rb
212
+ - app/finders/passwords.rb
213
+ - app/finders/passwords/wp_login.rb
214
+ - app/finders/passwords/xml_rpc.rb
215
+ - app/finders/passwords/xml_rpc_multicall.rb
210
216
  - app/finders/plugin_version.rb
211
217
  - app/finders/plugin_version/readme.rb
212
218
  - app/finders/plugins.rb
@@ -234,6 +240,7 @@ files:
234
240
  - app/finders/users/author_posts.rb
235
241
  - app/finders/users/login_error_messages.rb
236
242
  - app/finders/users/oembed_api.rb
243
+ - app/finders/users/rss_generator.rb
237
244
  - app/finders/users/wp_json_api.rb
238
245
  - app/finders/wp_items.rb
239
246
  - app/finders/wp_items/urls_in_homepage.rb
@@ -245,24 +252,22 @@ files:
245
252
  - app/finders/wp_version/unique_fingerprinting.rb
246
253
  - app/models.rb
247
254
  - app/models/config_backup.rb
255
+ - app/models/db_export.rb
248
256
  - app/models/interesting_finding.rb
249
257
  - app/models/media.rb
250
258
  - app/models/plugin.rb
251
259
  - app/models/theme.rb
252
260
  - app/models/timthumb.rb
253
- - app/models/user.rb
254
261
  - app/models/wp_item.rb
255
262
  - app/models/wp_version.rb
256
263
  - app/models/xml_rpc.rb
257
- - app/views/cli/brute_force/error.erb
258
- - app/views/cli/brute_force/found.erb
259
- - app/views/cli/brute_force/users.erb
260
264
  - app/views/cli/core/banner.erb
261
265
  - app/views/cli/core/db_update_finished.erb
262
266
  - app/views/cli/core/db_update_started.erb
263
267
  - app/views/cli/core/not_fully_configured.erb
264
268
  - app/views/cli/core/version.erb
265
269
  - app/views/cli/enumeration/config_backups.erb
270
+ - app/views/cli/enumeration/db_exports.erb
266
271
  - app/views/cli/enumeration/medias.erb
267
272
  - app/views/cli/enumeration/plugins.erb
268
273
  - app/views/cli/enumeration/themes.erb
@@ -272,18 +277,19 @@ files:
272
277
  - app/views/cli/info.erb
273
278
  - app/views/cli/main_theme/theme.erb
274
279
  - app/views/cli/notice.erb
280
+ - app/views/cli/password_attack/users.erb
275
281
  - app/views/cli/theme.erb
276
282
  - app/views/cli/usage.erb
277
283
  - app/views/cli/vulnerability.erb
278
284
  - app/views/cli/wp_item.erb
279
285
  - app/views/cli/wp_version/version.erb
280
- - app/views/json/brute_force/users.erb
281
286
  - app/views/json/core/banner.erb
282
287
  - app/views/json/core/db_update_finished.erb
283
288
  - app/views/json/core/db_update_started.erb
284
289
  - app/views/json/core/not_fully_configured.erb
285
290
  - app/views/json/core/version.erb
286
291
  - app/views/json/enumeration/config_backups.erb
292
+ - app/views/json/enumeration/db_exports.erb
287
293
  - app/views/json/enumeration/medias.erb
288
294
  - app/views/json/enumeration/plugins.erb
289
295
  - app/views/json/enumeration/themes.erb
@@ -291,6 +297,7 @@ files:
291
297
  - app/views/json/enumeration/users.erb
292
298
  - app/views/json/finding.erb
293
299
  - app/views/json/main_theme/theme.erb
300
+ - app/views/json/password_attack/users.erb
294
301
  - app/views/json/theme.erb
295
302
  - app/views/json/wp_item.erb
296
303
  - app/views/json/wp_version/version.erb
@@ -1,116 +0,0 @@
1
- module WPScan
2
- module Controller
3
- # Brute Force Controller
4
- class BruteForce < CMSScanner::Controller::Base
5
- def cli_options
6
- [
7
- OptFilePath.new(
8
- ['--passwords FILE-PATH', '-P',
9
- 'List of passwords to use during the brute forcing.',
10
- 'If no --username/s option supplied, user enumeration will be run'],
11
- exists: true
12
- ),
13
- OptSmartList.new(['--usernames LIST', '-U', 'List of usernames to use during the brute forcing'])
14
- ]
15
- end
16
-
17
- def run
18
- return unless parsed_options[:passwords]
19
-
20
- begin
21
- found = []
22
-
23
- brute_force(users, passwords(parsed_options[:passwords])) do |user|
24
- found << user
25
-
26
- output('found', user: user) if user_interaction?
27
- end
28
- ensure
29
- output('users', users: found)
30
- end
31
- end
32
-
33
- # @return [ Array<Users> ] The users to brute force
34
- def users
35
- return target.users unless parsed_options[:usernames]
36
-
37
- parsed_options[:usernames].reduce([]) do |acc, elem|
38
- acc << User.new(elem.chomp)
39
- end
40
- end
41
-
42
- # the iteration should be on the passwords to be more efficient
43
- # however, it's not that simple expecially when a combination is found:
44
- # - the estimated number of requests (for the progressbar) has to be updated.
45
- # - the user found has to be deleted from the loop
46
- #
47
- # @param [ Array<User> ] users
48
- # @param [ Array<String> ] passwords
49
- #
50
- # @yield [ User ] when a valid combination is found
51
- def brute_force(users, passwords)
52
- hydra = Browser.instance.hydra
53
-
54
- users.each do |user|
55
- bar = progress_bar(passwords.size, user.username) if user_interaction?
56
-
57
- passwords.each do |password|
58
- request = target.login_request(user.username, password)
59
-
60
- request.on_complete do |res|
61
- bar.progress += 1 if user_interaction?
62
-
63
- if res.code == 302
64
- user.password = password
65
- hydra.abort
66
-
67
- yield user
68
- next
69
- elsif user_interaction? && res.code != 200
70
- # Errors not displayed when using formats other than cli/cli-no-colour
71
- output_error(res)
72
- end
73
- end
74
-
75
- hydra.queue(request)
76
- end
77
- hydra.run
78
- end
79
- end
80
-
81
- def progress_bar(size, username)
82
- ProgressBar.create(
83
- format: '%t %a <%B> (%c / %C) %P%% %e',
84
- title: "Brute Forcing #{username} -",
85
- total: size
86
- )
87
- end
88
-
89
- # @param [ String ] wordlist_path
90
- #
91
- # @return [ Array<String> ]
92
- def passwords(wordlist_path)
93
- @passwords ||= File.open(wordlist_path).reduce([]) do |acc, elem|
94
- acc << elem.chomp
95
- end
96
- end
97
-
98
- # @param [ Typhoeus::Response ] response
99
- def output_error(response)
100
- return if response.body =~ /login_error/i
101
-
102
- error = if response.timed_out?
103
- 'Request timed out.'
104
- elsif response.code.zero?
105
- "No response from remote server. WAF/IPS? (#{response.return_message})"
106
- elsif response.code.to_s =~ /^50/
107
- 'Server error, try reducing the number of threads.'
108
- else
109
- "Unknown response received Code: #{response.code}\n Body: #{response.body}"
110
- end
111
-
112
- output('error', msg: error)
113
- end
114
- end
115
- end
116
- end