python.django.security.passwords.use-none-for-password-default.use-none-for-password-default

Verifed by r2c
Community Favorite
profile photo of semgrepsemgrep
Author
100,049
Download Count*

'$VAR' is using the empty string as its default and is being used to set the password on '$MODEL'. If you meant to set an unusable password, set the default value to 'None' or call 'set_unusable_password()'.

Run Locally

Run in CI

Defintion

rules:
  - id: use-none-for-password-default
    message: "'$VAR' is using the empty string as its default and is being used to
      set the password on '$MODEL'. If you meant to set an unusable password,
      set the default value to 'None' or call 'set_unusable_password()'."
    metadata:
      cwe:
        - "CWE-521: Weak Password Requirements"
      owasp:
        - A07:2021 - Identification and Authentication Failures
      references:
        - https://docs.djangoproject.com/en/3.0/ref/contrib/auth/#django.contrib.auth.models.User.set_password
      category: security
      technology:
        - django
      subcategory:
        - vuln
      likelihood: MEDIUM
      impact: MEDIUM
      confidence: MEDIUM
      license: Commons Clause License Condition v1.0[LGPL-2.1-only]
      vulnerability_class:
        - Improper Authentication
    languages:
      - python
    severity: ERROR
    patterns:
      - pattern-either:
          - pattern: |
              $VAR = request.$W.get($X, $EMPTY)
              ...
              $MODEL.set_password($VAR)
              ...
              $MODEL.save(...)
          - pattern: |
              def $F(..., $VAR=$EMPTY, ...):
                ...
                $MODEL.set_password($VAR)
      - metavariable-pattern:
          metavariable: $EMPTY
          pattern: '""'
      - focus-metavariable: $EMPTY
    fix: |
      None

Examples

use-none-for-password-default.py

from django.contrib import auth
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import PermissionDenied, ValidationError
from django.utils.translation import gettext as _
from django.views.decorators.csrf import csrf_protect
from rest_framework import status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response

from ...conf import settings
from ...core.decorators import require_dict_data
from ...core.mail import mail_user
from ..bans import get_user_ban
from ..forms.auth import AuthenticationForm, ResendActivationForm, ResetPasswordForm
from ..serializers import AnonymousUserSerializer, AuthenticatedUserSerializer
from ..tokens import (
    is_password_change_token_valid,
    make_activation_token,
    make_password_change_token,
)
from .rest_permissions import UnbannedAnonOnly, UnbannedOnly

User = auth.get_user_model()
BaseUserManager = User.__class__

class PasswordChangeFailed(Exception):
    pass

def change_forgotten_password(request, pk, token):
    """
    POST /auth/change-password/user/token/ with CSRF and new password
    will change forgotten password
    """
    if request.settings.enable_sso:
        raise PermissionDenied(_("Please use the 3rd party site to authenticate."))

    invalid_message = _("Form link is invalid. Please try again.")
    expired_message = _("Your link has expired. Please request new one.")

    try:
        try:
            user = User.objects.get(pk=pk, is_active=True)
        except User.DoesNotExist:
            raise PasswordChangeFailed(invalid_message)

        if request.user.is_authenticated and request.user.id != user.id:
            raise PasswordChangeFailed(invalid_message)
        if not is_password_change_token_valid(user, token):
            raise PasswordChangeFailed(invalid_message)

        if user.requires_activation:
            raise PasswordChangeFailed(expired_message)
        if get_user_ban(user, request.cache_versions):
            raise PasswordChangeFailed(expired_message)
    except PasswordChangeFailed as e:
        return Response({"detail": e.args[0]}, status=status.HTTP_400_BAD_REQUEST)

    try:
        # ruleid: use-none-for-password-default
        new_password = request.data.get("password", "")
        validate_password(new_password, user=user)
        user.set_password(new_password)
        user.save()
    except ValidationError as e:
        return Response({"detail": e.messages[0]}, status=status.HTTP_400_BAD_REQUEST)

    return Response({"username": user.username})

class UserManager(BaseUserManager):
    # ruleid: use-none-for-password-default
    def create_user(self, email, password=""):
        """
        Creates and saves a Poster with the given email and password.
        """
        if not email:
            raise ValueError('Users must have an email address')

        user = self.model(email=self.normalize_email(email))
        user.set_password(password)
        user.save(using=self._db)
        return user

# ok: use-none-for-password-default
def foo2(password="helloworld"):
	model.set_password(password)