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