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
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