Email uniqueness not properly verified - Python

Email uniqueness not properly verified - Python

Need

Enforce email uniqueness during registration process

Context

  • Usage of Python 3 for writing and executing Python code
  • Usage of Django for building web applications

Description

Non compliant code

        from django.db import models

class User(models.Model):
    email = models.EmailField(unique=True)
    # other fields...
        
        

The above code represents a User model in Django where the email field is set to be unique. Django's built-in validation will ensure that the email is in the correct format and that it is unique across the User model. However, it does not account for the possibility of the same email being used with different plus characters.

For example, an attacker could register multiple accounts with the same email by adding a plus character followed by different words. All emails sent to these addresses would go to the same inbox. For instance, attacker@gmail.com, attacker+1@gmail.com, attacker+2@gmail.com etc., are all considered unique by the system but in reality, they all point to the same email inbox.

This could be potentially exploited by an attacker to create multiple accounts, bypassing any limitations on account numbers per email address. In addition, any communication sent to these emails by the system would all go to the same person, potentially causing confusion or enabling misuse.

Steps

  • Implement a custom validation function for the email field in the User model.
  • In this function, normalize the email address by removing any characters after and including the '+' sign before the '@' sign.
  • Use this normalized email for the uniqueness check instead of the original email.
  • Ensure that this validation function is called whenever a new User object is created or an existing one is updated.

Compliant code

        from django.core.exceptions import ValidationError
from django.db import models

class User(models.Model):
    email = models.EmailField(unique=True)
    # other fields...

    def clean(self):
        # Normalize the email by removing the '+' and everything after it
        local, domain = self.email.split('@')
        if '+' in local:
            local = local.split('+')[0]
        self.email = local + '@' + domain

        # Check for uniqueness of the email
        if User.objects.filter(email__iexact=self.email).exists():
            raise ValidationError("Email already exists")

        super(User, self).clean()
        
        

The above code fixes the vulnerability by implementing a custom validation function clean in the User model.

In this function, the email address is first split into the local part and the domain part. If the local part contains a '+', everything from the '+' character onwards is removed. The email address is then reconstructed and assigned back to self.email.

Next, a check is performed to see if a User object with the same email (ignoring case) already exists in the database. If such an object exists, a ValidationError is raised. This ensures that the email address is unique across all User objects.

Finally, the clean method of the superclass is called to perform any additional validation provided by Django.

This validation function is automatically called whenever a User object is saved. This includes both the creation of new User objects and the updating of existing ones. Therefore, this fix ensures that the email uniqueness constraint is properly enforced in all situations.

References