wpscan 3.2.1 → 3.3.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.
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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5faff8eba24f5f7d0ea97833dfb2200f23c1414b
4
- data.tar.gz: 905e1d1dec28cea9d51d094ed7af626233f131fa
3
+ metadata.gz: 5eac3c3174f8ae7798069fddfc45fd67f837b388
4
+ data.tar.gz: 626cfade1aeb099be9bfc1fbadf68a319a25fa55
5
5
  SHA512:
6
- metadata.gz: 10b9dc665fb06f2d3f422a02f432a64921bb855662627abf548aca9b9f45ee3cb6aa9f6e97edc71ba58f759f7eedebce90628849aa79f259e0ea1f1effba9049
7
- data.tar.gz: ae7fc0b48cd53132fd2c0d2e4212b05a798afbbbb7aac9737d0cddb79b164885431b49b786b02eca0ae228926e71c60f5b6dd7f4699e4e607a71832202ef6db7
6
+ metadata.gz: 031efaee33293d3ee7f0ff05a0a7100b8cf87b8b3f1429c513a6cb5ddd7d0a015fb6d7d462fb7fcc8cfbf18fd15c6ffe7db2428cb2ea77fed577c18055d975b4
7
+ data.tar.gz: b010b70018627700aa90cfebb98adf0f5f3de157690e1baa56885405725f1e16fa96c2d1b65bc19d73fbd6707146e2ab1c712ad283cf388753a26218a0bdd5a4
data/README.md CHANGED
@@ -3,7 +3,79 @@
3
3
  [![Gem Version](https://badge.fury.io/rb/wpscan.svg)](https://badge.fury.io/rb/wpscan)
4
4
  [![Build Status](https://travis-ci.org/wpscanteam/wpscan-v3.svg?branch=master)](https://travis-ci.org/wpscanteam/wpscan-v3)
5
5
  [![Code Climate](https://codeclimate.com/github/wpscanteam/wpscan-v3/badges/gpa.svg)](https://codeclimate.com/github/wpscanteam/wpscan-v3)
6
- [![Dependency Status](https://img.shields.io/gemnasium/wpscanteam/wpscan-v3.svg)](https://gemnasium.com/wpscanteam/wpscan-v3)
6
+ [![Patreon Donate](https://img.shields.io/badge/patreon-donate-green.svg)](https://www.patreon.com/wpscan)
7
+
8
+ # INSTALL
9
+
10
+ ## Prerequisites:
11
+
12
+ - Ruby >= 2.2.2 - Recommended: 2.3.3
13
+ - Curl >= 7.21 - Recommended: latest - FYI the 7.29 has a segfault
14
+ - RubyGems - Recommended: latest
15
+
16
+ ### From RubyGems:
17
+
18
+ ```
19
+ gem install wpscan
20
+ ```
21
+
22
+ ### From sources:
23
+
24
+ Prerequisites: Git
25
+
26
+ ```
27
+ git clone https://github.com/wpscanteam/wpscan-v3 wpscan
28
+
29
+ cd wpscan/
30
+
31
+ bundle install && rake install
32
+ ```
33
+
34
+ # Docker
35
+
36
+ Pull the repo with ```docker pull wpscanteam/wpscan-v3```
37
+
38
+ # Usage
39
+
40
+ ```wpscan --url blog.tld``` This will scan the blog using default options with a good compromise between speed and accuracy. For example, the plugins will be checked passively but their version with a mixed detection mode (passively + aggressively). Potential config backup files will also be checked, along with other interesting findings. If a more stealthy approach is required, then ```wpscan --stealthy --url blog.tld``` can be used.
41
+ As a result, when using the ```--enumerate``` option, don't forget to set the ```--plugins-detection``` accordingly, as its default is 'passive'.
42
+
43
+ For more options, open a terminal and type ```wpscan --help``` (if you built wpscan from the source, you should type the command outside of the git repo)
44
+
45
+ The DB is located at ~/.wpscan/db
46
+
47
+ WPScan can load all options (including the --url) from configuration files, the following locations are checked (order: first to last):
48
+
49
+ * ~/.wpscan/cli_options.json
50
+ * ~/.wpscan/cli_options.yml
51
+ * pwd/.wpscan/cli_options.json
52
+ * pwd/.wpscan/cli_options.yml
53
+
54
+ If those files exist, options from them will be loaded and overridden if found twice.
55
+
56
+ e.g:
57
+
58
+ ~/.wpscan/cli_options.yml:
59
+ ```
60
+ proxy: 'http://127.0.0.1:8080'
61
+ verbose: true
62
+ ```
63
+
64
+ pwd/.wpscan/cli_options.yml:
65
+ ```
66
+ proxy: 'socks5://127.0.0.1:9090'
67
+ url: 'http://target.tld'
68
+ ```
69
+
70
+ Running ```wpscan``` in the current directory (pwd), is the same as ```wpscan -v --proxy socks5://127.0.0.1:9090 --url http://target.tld```
71
+
72
+ # PROJECT HOME
73
+
74
+ [https://wpscan.org](https://wpscan.org)
75
+
76
+ # VULNERABILITY DATABASE
77
+
78
+ [https://wpvulndb.com](https://wpvulndb.com)
7
79
 
8
80
  # LICENSE
9
81
 
@@ -83,72 +155,3 @@ Running WPScan against websites without prior mutual consent may be illegal in y
83
155
  ### 11. Trademark
84
156
 
85
157
  The "wpscan" term is a registered trademark. This License does not grant the use of the "wpscan" trademark or the use of the WPScan logo.
86
-
87
- # INSTALL
88
-
89
- ## Prerequisites:
90
-
91
- - Ruby >= 2.2.2 - Recommended: 2.3.3
92
- - Curl >= 7.21 - Recommended: latest - FYI the 7.29 has a segfault
93
- - RubyGems - Recommended: latest
94
-
95
-
96
- ### From RubyGems:
97
-
98
- ```gem install wpscan```
99
-
100
- ### From sources:
101
-
102
- Prerequisites: Git
103
-
104
- ```git clone https://github.com/wpscanteam/wpscan-v3```
105
-
106
- ```cd wpscan```
107
-
108
- ```bundle install && rake install```
109
-
110
- # Docker
111
-
112
- Pull the repo with ```docker pull wpscanteam/wpscan-v3```
113
-
114
- # Usage
115
-
116
- ```wpscan --url blog.tld``` This will scan the blog using default options with a good compromise between speed and accuracy. For example, the plugins will be checked passively but their version with a mixed detection mode (passively + aggressively). Potential config backup files will also be checked, along with other interesting findings. If a more stealthy approach is required, then ```wpscan --stealthy --url blog.tld``` can be used.
117
- As a result, when using the ```--enumerate``` option, don't forget to set the ```--plugins-detection``` accordingly, as its default is 'passive'.
118
-
119
- For more options, open a terminal and type ```wpscan --help``` (if you built wpscan from the source, you should type the command outside of the git repo)
120
-
121
- The DB is located at ~/.wpscan/db
122
-
123
- WPScan can load all options (including the --url) from configuration files, the following locations are checked (order: first to last):
124
-
125
- * ~/.wpscan/cli_options.json
126
- * ~/.wpscan/cli_options.yml
127
- * pwd/.wpscan/cli_options.json
128
- * pwd/.wpscan/cli_options.yml
129
-
130
- If those files exist, options from them will be loaded and overridden if found twice.
131
-
132
- e.g:
133
-
134
- ~/.wpscan/cli_options.yml:
135
- ```
136
- proxy: 'http://127.0.0.1:8080'
137
- verbose: true
138
- ```
139
-
140
- pwd/.wpscan/cli_options.yml:
141
- ```
142
- proxy: 'socks5://127.0.0.1:9090'
143
- url: 'http://target.tld'
144
- ```
145
-
146
- Running ```wpscan``` in the current directory (pwd), is the same as ```wpscan -v --proxy socks5://127.0.0.1:9090 --url http://target.tld```
147
-
148
- # PROJECT HOME
149
-
150
- [https://wpscan.org](https://wpscan.org)
151
-
152
- # VULNERABILITY DATABASE
153
-
154
- [https://wpvulndb.com](https://wpvulndb.com)
data/app/controllers.rb CHANGED
@@ -3,5 +3,5 @@ require_relative 'controllers/custom_directories'
3
3
  require_relative 'controllers/wp_version'
4
4
  require_relative 'controllers/main_theme'
5
5
  require_relative 'controllers/enumeration'
6
- require_relative 'controllers/brute_force'
6
+ require_relative 'controllers/password_attack'
7
7
  require_relative 'controllers/aliases'
@@ -16,7 +16,7 @@ module WPScan
16
16
  enum_plugins if enum_plugins?(enum)
17
17
  enum_themes if enum_themes?(enum)
18
18
 
19
- %i[timthumbs config_backups medias].each do |key|
19
+ %i[timthumbs config_backups db_exports medias].each do |key|
20
20
  send("enum_#{key}".to_sym) if enum.key?(key)
21
21
  end
22
22
 
@@ -4,7 +4,8 @@ module WPScan
4
4
  class Enumeration < CMSScanner::Controller::Base
5
5
  def cli_options
6
6
  cli_enum_choices + cli_plugins_opts + cli_themes_opts +
7
- cli_timthumbs_opts + cli_config_backups_opts + cli_medias_opts + cli_users_opts
7
+ cli_timthumbs_opts + cli_config_backups_opts + cli_db_exports_opts +
8
+ cli_medias_opts + cli_users_opts
8
9
  end
9
10
 
10
11
  # @return [ Array<OptParseValidator::OptBase> ]
@@ -14,26 +15,27 @@ module WPScan
14
15
  OptMultiChoices.new(
15
16
  ['--enumerate [OPTS]', '-e', 'Enumeration Process'],
16
17
  choices: {
17
- vp: OptBoolean.new(['--vulnerable-plugins']),
18
- ap: OptBoolean.new(['--all-plugins']),
19
- p: OptBoolean.new(['--plugins']),
20
- vt: OptBoolean.new(['--vulnerable-themes']),
21
- at: OptBoolean.new(['--all-themes']),
22
- t: OptBoolean.new(['--themes']),
23
- tt: OptBoolean.new(['--timthumbs']),
24
- cb: OptBoolean.new(['--config-backups']),
25
- u: OptIntegerRange.new(['--users', 'User ids range. e.g: u1-5'], value_if_empty: '1-10'),
26
- m: OptIntegerRange.new(['--medias', 'Media ids range. e.g m1-15'], value_if_empty: '1-100')
18
+ vp: OptBoolean.new(['--vulnerable-plugins']),
19
+ ap: OptBoolean.new(['--all-plugins']),
20
+ p: OptBoolean.new(['--plugins']),
21
+ vt: OptBoolean.new(['--vulnerable-themes']),
22
+ at: OptBoolean.new(['--all-themes']),
23
+ t: OptBoolean.new(['--themes']),
24
+ tt: OptBoolean.new(['--timthumbs']),
25
+ cb: OptBoolean.new(['--config-backups']),
26
+ dbe: OptBoolean.new(['--db-exports']),
27
+ u: OptIntegerRange.new(['--users', 'User IDs range. e.g: u1-5'], value_if_empty: '1-10'),
28
+ m: OptIntegerRange.new(['--medias', 'Media IDs range. e.g m1-15'], value_if_empty: '1-100')
27
29
  },
28
- value_if_empty: 'vp,vt,tt,cb,u,m',
30
+ value_if_empty: 'vp,vt,tt,cb,dbe,u,m',
29
31
  incompatible: [%i[vp ap p], %i[vt at t]],
30
32
  default: { all_plugins: true, config_backups: true }
31
33
  ),
32
34
  OptRegexp.new(
33
35
  [
34
36
  '--exclude-content-based REGEXP_OR_STRING',
35
- 'Exclude all responses having their body matching (case insensitive) during parts of the enumeration.',
36
- 'Regexp delimiters are not required.'
37
+ 'Exclude all responses matching the Regexp (case insensitive) during parts of the enumeration.',
38
+ 'Both the headers and body are checked. Regexp delimiters are not required.'
37
39
  ], options: Regexp::IGNORECASE
38
40
  )
39
41
  ]
@@ -110,7 +112,22 @@ module WPScan
110
112
  ),
111
113
  OptChoice.new(
112
114
  ['--config-backups-detection MODE',
113
- 'Use the supplied mode to enumerate Configs, instead of the global (--detection-mode) mode.'],
115
+ 'Use the supplied mode to enumerate Config Backups, instead of the global (--detection-mode) mode.'],
116
+ choices: %w[mixed passive aggressive], normalize: :to_sym
117
+ )
118
+ ]
119
+ end
120
+
121
+ # @return [ Array<OptParseValidator::OptBase> ]
122
+ def cli_db_exports_opts
123
+ [
124
+ OptFilePath.new(
125
+ ['--db-exports-list FILE-PATH', 'List of DB exports\' paths to use'],
126
+ exists: true, default: File.join(DB_DIR, 'db_exports.txt')
127
+ ),
128
+ OptChoice.new(
129
+ ['--db-exports-detection MODE',
130
+ 'Use the supplied mode to enumerate DB Exports, instead of the global (--detection-mode) mode.'],
114
131
  choices: %w[mixed passive aggressive], normalize: :to_sym
115
132
  )
116
133
  ]
@@ -136,6 +136,13 @@ module WPScan
136
136
  output('config_backups', config_backups: target.config_backups(opts))
137
137
  end
138
138
 
139
+ def enum_db_exports
140
+ opts = default_opts('db_exports').merge(list: parsed_options[:db_exports_list])
141
+
142
+ output('@info', msg: 'Enumerating DB Exports') if user_interaction?
143
+ output('db_exports', db_exports: target.db_exports(opts))
144
+ end
145
+
139
146
  def enum_medias
140
147
  opts = default_opts('medias').merge(range: parsed_options[:enumerate][:medias])
141
148
 
@@ -0,0 +1,108 @@
1
+ module WPScan
2
+ module Controller
3
+ # Password Attack Controller
4
+ class PasswordAttack < CMSScanner::Controller::Base
5
+ def cli_options
6
+ [
7
+ OptFilePath.new(
8
+ ['--passwords FILE-PATH', '-P',
9
+ 'List of passwords to use during the password attack.',
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 password attack.']),
14
+ OptInteger.new(['--multicall-max-passwords MAX_PWD',
15
+ 'Maximum number of passwords to send by request with XMLRPC multicall'],
16
+ default: 500),
17
+ OptChoice.new(['--password-attack ATTACK',
18
+ 'Force the supplied attack to be used rather than automatically determining one.'],
19
+ choices: %w[wp-login xmlrpc xmlrpc-multicall],
20
+ normalize: %i[downcase underscore to_sym])
21
+ ]
22
+ end
23
+
24
+ def run
25
+ return unless parsed_options[:passwords]
26
+
27
+ if user_interaction?
28
+ output('@info',
29
+ msg: "Performing password attack on #{attacker.titleize} against #{users.size} user/s")
30
+ end
31
+
32
+ attack_opts = {
33
+ show_progression: user_interaction?,
34
+ multicall_max_passwords: parsed_options[:multicall_max_passwords]
35
+ }
36
+
37
+ begin
38
+ found = []
39
+
40
+ attacker.attack(users, passwords(parsed_options[:passwords]), attack_opts) do |user|
41
+ found << user
42
+
43
+ attacker.progress_bar.log("[SUCCESS] - #{user.username} / #{user.password}")
44
+ end
45
+ ensure
46
+ output('users', users: found)
47
+ end
48
+ end
49
+
50
+ # @return [ CMSScanner::Finders::Finder ] The finder used to perform the attack
51
+ def attacker
52
+ @attacker ||= attacker_from_cli_options || attacker_from_automatic_detection
53
+ end
54
+
55
+ # @return [ WPScan::XMLRPC ]
56
+ def xmlrpc
57
+ @xmlrpc ||= target.xmlrpc
58
+ end
59
+
60
+ # @return [ CMSScanner::Finders::Finder ]
61
+ def attacker_from_cli_options
62
+ return unless parsed_options[:password_attack]
63
+
64
+ case parsed_options[:password_attack]
65
+ when :wp_login
66
+ WPScan::Finders::Passwords::WpLogin.new(target)
67
+ when :xmlrpc
68
+ WPScan::Finders::Passwords::XMLRPC.new(xmlrpc)
69
+ when :xmlrpc_multicall
70
+ WPScan::Finders::Passwords::XMLRPCMulticall.new(xmlrpc)
71
+ end
72
+ end
73
+
74
+ # @return [ CMSScanner::Finders::Finder ]
75
+ def attacker_from_automatic_detection
76
+ if xmlrpc&.enabled? && xmlrpc.available_methods.include?('wp.getUsersBlogs')
77
+ wp_version = target.wp_version
78
+
79
+ if wp_version && wp_version < '4.4'
80
+ WPScan::Finders::Passwords::XMLRPCMulticall.new(xmlrpc)
81
+ else
82
+ WPScan::Finders::Passwords::XMLRPC.new(xmlrpc)
83
+ end
84
+ else
85
+ WPScan::Finders::Passwords::WpLogin.new(target)
86
+ end
87
+ end
88
+
89
+ # @return [ Array<Users> ] The users to brute force
90
+ def users
91
+ return target.users unless parsed_options[:usernames]
92
+
93
+ parsed_options[:usernames].reduce([]) do |acc, elem|
94
+ acc << CMSScanner::User.new(elem.chomp)
95
+ end
96
+ end
97
+
98
+ # @param [ String ] wordlist_path
99
+ #
100
+ # @return [ Array<String> ]
101
+ def passwords(wordlist_path)
102
+ @passwords ||= File.open(wordlist_path).reduce([]) do |acc, elem|
103
+ acc << elem.chomp
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
data/app/finders.rb CHANGED
@@ -5,9 +5,11 @@ require_relative 'finders/main_theme'
5
5
  require_relative 'finders/timthumb_version'
6
6
  require_relative 'finders/timthumbs'
7
7
  require_relative 'finders/config_backups'
8
+ require_relative 'finders/db_exports'
8
9
  require_relative 'finders/medias'
9
10
  require_relative 'finders/users'
10
11
  require_relative 'finders/plugins'
11
12
  require_relative 'finders/plugin_version'
12
13
  require_relative 'finders/theme_version'
13
14
  require_relative 'finders/themes'
15
+ require_relative 'finders/passwords'
@@ -9,7 +9,7 @@ module WPScan
9
9
  # @option opts [ String ] :list
10
10
  # @option opts [ Boolean ] :show_progression
11
11
  #
12
- # @return [ Array<InterestingFinding> ]
12
+ # @return [ Array<ConfigBackup> ]
13
13
  def aggressive(opts = {})
14
14
  found = []
15
15
 
@@ -0,0 +1,17 @@
1
+ require_relative 'db_exports/known_locations'
2
+
3
+ module WPScan
4
+ module Finders
5
+ module DbExports
6
+ # DB Exports Finder
7
+ class Base
8
+ include CMSScanner::Finders::SameTypeFinder
9
+
10
+ # @param [ WPScan::Target ] target
11
+ def initialize(target)
12
+ finders << DbExports::KnownLocations.new(target)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,49 @@
1
+ module WPScan
2
+ module Finders
3
+ module DbExports
4
+ # DB Exports finder
5
+ # See https://github.com/wpscanteam/wpscan-v3/issues/62
6
+ class KnownLocations < CMSScanner::Finders::Finder
7
+ include CMSScanner::Finders::Finder::Enumerator
8
+
9
+ # @param [ Hash ] opts
10
+ # @option opts [ String ] :list
11
+ # @option opts [ Boolean ] :show_progression
12
+ #
13
+ # @return [ Array<DBExport> ]
14
+ def aggressive(opts = {})
15
+ found = []
16
+
17
+ enumerate(potential_urls(opts), opts) do |res|
18
+ next unless res.code == 200 && res.body =~ /INSERT INTO/
19
+
20
+ found << WPScan::DbExport.new(res.request.url, found_by: DIRECT_ACCESS, confidence: 100)
21
+ end
22
+
23
+ found
24
+ end
25
+
26
+ # @param [ Hash ] opts
27
+ # @option opts [ String ] :list Mandatory
28
+ #
29
+ # @return [ Hash ]
30
+ def potential_urls(opts = {})
31
+ urls = {}
32
+ domain_name = target.uri.host[/(^[\w|-]+)/, 1]
33
+
34
+ File.open(opts[:list]).each_with_index do |path, index|
35
+ path.gsub!('{domain_name}', domain_name)
36
+
37
+ urls[target.url(path.chomp)] = index
38
+ end
39
+
40
+ urls
41
+ end
42
+
43
+ def create_progress_bar(opts = {})
44
+ super(opts.merge(title: ' Checking DB Exports -'))
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end