Secure password reset functionality
from django.contrib.auth.tokens import default_token_generator
from django.core.mail import send_mail
from django.contrib.sites.shortcuts import get_current_site
from django.utils.http import urlsafe_base64_encode
from django.template.loader import render_to_string
from django.utils.encoding import force_bytes
from django.contrib.auth.models import User
def password_reset(request):
email = request.POST['email']
user = User.objects.get(email=email)
token = default_token_generator.make_token(user)
domain = get_current_site(request).domain
uid = urlsafe_base64_encode(force_bytes(user.pk))
password_reset_url = f'http://{domain}/reset/{uid}/{token}'
message = render_to_string('reset_password_email.html', {
'password_reset_url': password_reset_url
})
send_mail('Password reset on %s' % domain,
message, None, [user.email])
The above code is a Django view function that is vulnerable to password reset poisoning.
When a user requests a password reset, the application generates a unique URL that the user can use to reset their password. This URL is sent to the user's email address. The URL contains a token that is generated based on the user's details and is used to verify the identity of the user when they click on the link.
The vulnerability lies in the use of the
get_current_site(request).domain
method to get the domain for the password reset URL. This method gets the current site from the
request
object, which can be manipulated by an attacker.
An attacker can change the
Host
header in the HTTP request to a domain that they control. When the application generates the password reset URL, it will use the attacker's domain instead of the legitimate one. The attacker can then intercept the password reset URL and use it to reset the user's password.
This is a serious vulnerability as it can lead to account takeover attacks. The attacker can gain unauthorized access to the user's account and perform actions on their behalf.
from django.contrib.auth.tokens import default_token_generator
from django.core.mail import send_mail
from django.contrib.sites.shortcuts import get_current_site
from django.utils.http import urlsafe_base64_encode
from django.template.loader import render_to_string
from django.utils.encoding import force_bytes
from django.contrib.auth.models import User
from django.conf import settings
from django.core.exceptions import ValidationError
from django.http import HttpResponseBadRequest
def validate_domain(domain):
if domain not in settings.ALLOWED_HOSTS:
raise ValidationError("Untrusted domain")
def password_reset(request):
email = request.POST['email']
user = User.objects.get(email=email)
token = default_token_generator.make_token(user)
domain = get_current_site(request).domain
validate_domain(domain)
uid = urlsafe_base64_encode(force_bytes(user.pk))
password_reset_url = f'https://{domain}/reset/{uid}/{token}'
message = render_to_string('reset_password_email.html', {
'password_reset_url': password_reset_url
})
send_mail('Password reset on %s' % domain,
message, None, [user.email])
The updated code includes a function
validate_domain
that checks if the domain is in the list of
ALLOWED_HOSTS
in the settings. If the domain is not in the list, a
ValidationError
is raised. This ensures that the password reset link is only generated for trusted domains.
The password reset link now uses HTTPS instead of HTTP. This ensures that the communication between the client and the server is secure and the password reset link cannot be intercepted.
The
default_token_generator
function from Django's auth tokens is used to generate a unique token for the password reset. This token is tied to the user's session and cannot be used if the session ID changes.
The
send_mail
function is used to send the password reset email to the user. The email contains the password reset link with the secure token.
The password reset request is tied to the user's session, and rate limiting should be implemented at a higher level (e.g., at the web server or load balancer level) to prevent brute force attacks.