Secure password reset functionality
import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf_router/shelf_router.dart';
class UserController {
Router _router;
UserController() {
_router = Router();
_router.post('/reset-password', (shelf.Request request) async {
final params = await request.readAsString();
final email = params['email'];
// Generate reset link
final resetLink = 'http://yourapp.com/reset?token=123456&email=$email';
// Send the reset link to the user's email
// This is just a placeholder. In a real world scenario, you would use an email service.
print('Password reset link: $resetLink');
return shelf.Response.ok('Password reset link sent.');
});
}
shelf.Handler get handler => _router;
}
In the above Dart code, we have a
UserController
class that handles user related operations. It uses the
shelf_router
package to define routes for the application.
The
/reset-password
POST endpoint is used to generate a password reset link for the user. It reads the email from the request, generates a reset link, and sends it to the user's email.
The vulnerability lies in the generation of the reset link. The domain of the reset link is hard-coded as
http://yourapp.com
. An attacker can manipulate the request and change the domain to one under his control.
When the reset link is clicked, the request goes to the attacker's server with the reset token and the user's email. This allows the attacker to reset the user's password, effectively gaining control over the user's account.
This is a classic example of Password Reset Poisoning where the application does not properly validate the reset link's domain.
import 'dart:math';
import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf_router/shelf_router.dart';
class UserController {
Router _router;
Map tokens = {};
UserController() {
_router = Router();
_router.post('/reset-password', (shelf.Request request) async {
final params = await request.readAsString();
final email = params['email'];
// Generate a unique, random token for each password reset request
final token = _generateToken();
// Associate the token with the user's email on the server side
tokens[token] = email;
// Generate reset link
final resetLink = 'https://yourapp.com/reset?token=$token';
// Send the reset link to the user's email
// This is just a placeholder. In a real world scenario, you would use an email service.
print('Password reset link: $resetLink');
return shelf.Response.ok('Password reset link sent.');
});
}
String _generateToken() {
var rng = Random();
var codeUnits = List.generate(20, (index){
return rng.nextInt(33)+89;
});
return String.fromCharCodes(codeUnits);
}
shelf.Handler get handler => _router;
}
The updated code now generates a unique, random token for each password reset request. This token is associated with the user's email on the server side, and is included in the password reset link. The link now uses HTTPS to ensure secure communication.
The
_generateToken
method generates a random 20-character string to be used as the token. This token is stored in a map along with the user's email.
When the user clicks on the reset link, the server should validate the token and check if it's expired before allowing the password reset. This validation is not included in the code above, but should be implemented in the route handling the password reset.
The email is no longer included in the reset link, preventing an attacker from changing the email parameter to one under his control. The token is associated with the user's email on the server side, ensuring that the password reset is performed for the correct user.
The token should be set to expire after a certain period of time or after it's used to further enhance security. This is not included in the code above, but should be implemented in your application.