Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
wpscanteam
GitHub Repository: wpscanteam/wpscan
Path: blob/master/app/controllers/password_attack.rb
485 views
1
# frozen_string_literal: true
2
3
module WPScan
4
module Controller
5
# Password Attack Controller
6
class PasswordAttack < CMSScanner::Controller::Base
7
def cli_options
8
[
9
OptFilePath.new(
10
['--passwords FILE-PATH', '-P',
11
'List of passwords to use during the password attack.',
12
'If no --username/s option supplied, user enumeration will be run.'],
13
exists: true
14
),
15
OptSmartList.new(['--usernames LIST', '-U', 'List of usernames to use during the password attack.']),
16
OptInteger.new(['--multicall-max-passwords MAX_PWD',
17
'Maximum number of passwords to send by request with XMLRPC multicall'],
18
default: 500),
19
OptChoice.new(['--password-attack ATTACK',
20
'Force the supplied attack to be used rather than automatically determining one.',
21
'Multicall will only work against WP < 4.4'],
22
choices: %w[wp-login xmlrpc xmlrpc-multicall],
23
normalize: %i[downcase underscore to_sym]),
24
OptString.new(['--login-uri URI', 'The URI of the login page if different from /wp-login.php'])
25
]
26
end
27
28
def attack_opts
29
@attack_opts ||= {
30
show_progression: user_interaction?,
31
multicall_max_passwords: ParsedCli.multicall_max_passwords
32
}
33
end
34
35
def run
36
return unless ParsedCli.passwords
37
38
begin
39
found = []
40
41
if user_interaction?
42
output('@info',
43
msg: "Performing password attack on #{attacker.titleize} against #{users.size} user/s")
44
end
45
46
attacker.attack(users, ParsedCli.passwords, attack_opts) do |user|
47
found << user
48
49
attacker.progress_bar.log("[SUCCESS] - #{user.username} / #{user.password}")
50
end
51
rescue Error::NoLoginInterfaceDetected => e
52
# TODO: Maybe output that in JSON as well.
53
output('@notice', msg: e.to_s) if user_interaction?
54
ensure
55
output('users', users: found)
56
end
57
end
58
59
# @return [ CMSScanner::Finders::Finder ] The finder used to perform the attack
60
def attacker
61
@attacker ||= attacker_from_cli_options || attacker_from_automatic_detection
62
end
63
64
# @return [ Model::XMLRPC ]
65
def xmlrpc
66
@xmlrpc ||= target.xmlrpc
67
end
68
69
# @return [ CMSScanner::Finders::Finder ]
70
def attacker_from_cli_options
71
return unless ParsedCli.password_attack
72
73
case ParsedCli.password_attack
74
when :wp_login
75
raise Error::NoLoginInterfaceDetected unless target.login_url
76
77
Finders::Passwords::WpLogin.new(target)
78
when :xmlrpc
79
raise Error::XMLRPCNotDetected unless xmlrpc
80
81
Finders::Passwords::XMLRPC.new(xmlrpc)
82
when :xmlrpc_multicall
83
raise Error::XMLRPCNotDetected unless xmlrpc
84
85
Finders::Passwords::XMLRPCMulticall.new(xmlrpc)
86
end
87
end
88
89
# @return [ Boolean ]
90
def xmlrpc_get_users_blogs_enabled?
91
if xmlrpc&.enabled? &&
92
xmlrpc.available_methods.include?('wp.getUsersBlogs') &&
93
!xmlrpc.method_call('wp.getUsersBlogs', [SecureRandom.hex[0, 6], SecureRandom.hex[0, 4]])
94
.run.body.match?(/>\s*405\s*</)
95
96
true
97
else
98
false
99
end
100
end
101
102
# @return [ CMSScanner::Finders::Finder ]
103
def attacker_from_automatic_detection
104
if xmlrpc_get_users_blogs_enabled?
105
wp_version = target.wp_version
106
107
if wp_version && wp_version < '4.4'
108
Finders::Passwords::XMLRPCMulticall.new(xmlrpc)
109
else
110
Finders::Passwords::XMLRPC.new(xmlrpc)
111
end
112
elsif target.login_url
113
Finders::Passwords::WpLogin.new(target)
114
else
115
raise Error::NoLoginInterfaceDetected
116
end
117
end
118
119
# @return [ Array<Users> ] The users to brute force
120
def users
121
return target.users unless ParsedCli.usernames
122
123
ParsedCli.usernames.reduce([]) do |acc, elem|
124
acc << Model::User.new(elem.chomp)
125
end
126
end
127
end
128
end
129
end
130
131