Django Empty Password in set_password()

HIGH

Empty string passed to set_password() creates an account with no password protection. Use None or set_unusable_password() instead.

Rule Information

Language
Python
Category
Django
Author
Shivasurya
Shivasurya
Last Updated
2026-03-22
Tags
pythondjangopasswordauthenticationempty-passwordset-passwordauditCWE-521CWE-258OWASP-A07
CWE References

Interactive Playground

Experiment with the vulnerable code and security rule below. Edit the code to see how the rule detects different vulnerability patterns.

pathfinder scan --ruleset python/PYTHON-DJANGO-SEC-080 --project .
1
2
3
4
5
6
7
8
9
10
11
rule.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

About This Rule

Understanding the vulnerability and how it is detected

This rule detects cases in Django applications where set_password() is called with an empty string literal ('') as the password argument, which creates user accounts with no password protection.

Django's set_password() stores a hashed version of the provided password in the database. When called with an empty string, it stores a valid hash of the empty string, meaning the account is accessible without providing any password. This is distinctly different from the correct way to create passwordless accounts: using None (which Django stores as an unusable password hash "!") or calling set_unusable_password() explicitly.

The rule uses both pattern matching (calls("*.set_password") audit) and taint analysis to catch cases where the empty string default value flows from request.POST.get('password', '') into set_password().

Security Implications

Potential attack scenarios if this vulnerability is exploited

1

Authentication Bypass via Empty Password

An account created with set_password('') allows anyone to log in by submitting an empty password field. Depending on the application's authentication flow, this may allow unauthorized access to admin accounts, service accounts, or user accounts created through automated workflows.

2

Privilege Escalation via Service Account Takeover

Service accounts or integration accounts sometimes have passwords set to empty strings during development or provisioning. If these accounts have elevated privileges (staff=True, is_superuser=True), an empty password creates a trivial privilege escalation vector for any authenticated or unauthenticated user.

3

Password Reset Flow Bypass

If a password reset workflow calls set_password('') as an intermediate step (e.g., to "clear" the password before sending a reset link), the account is unprotected during the time between clearing and the user setting a new password.

4

Automated Account Provisioning Vulnerability

Automated user provisioning scripts that create accounts with empty passwords as placeholders create a window of vulnerability. If a provisioning job runs but the user setup is never completed, the empty-password account remains accessible indefinitely.

How to Fix

Recommended remediation steps

  • 1Never call set_password('') with an empty string; use set_unusable_password() when password-based login should be disabled.
  • 2Use None as the password value when creating accounts that should not be accessible via password, as Django stores this as the unusable password marker.
  • 3Always validate password input with validate_password() before calling set_password() to enforce minimum strength requirements.
  • 4Use request.POST.get('password') without a default value (or default to None) so that missing passwords are caught as an error rather than silently set to empty.
  • 5Add Django's built-in password validators in AUTH_PASSWORD_VALIDATORS settings to enforce complexity requirements application-wide.

Detection Scope

How Code Pathfinder analyzes your code for this vulnerability

This rule uses QueryType pattern matching on calls("*.set_password") to audit all set_password() call sites, specifically detecting calls where the argument is a literal empty string '' (tracked via .where() on the argument value). It also performs taint analysis to detect flows where request.POST.get('password', '') (the empty string default case) flows into set_password() without an intervening null/empty check. The .where() clause constrains matches to Python files in Django project structures.

Compliance & Standards

Industry frameworks and regulations that require detection of this vulnerability

OWASP Top 10
A07:2021 - Identification and Authentication Failures
CWE Top 25
CWE-521 - Weak Password Requirements in dangerous weaknesses list
PCI DSS v4.0
Requirement 8.3.1 - all user IDs and authentication factors managed per security policy
NIST SP 800-53
IA-5: Authenticator Management
SOC 2 Type II
CC6.1 - Logical access controls including authentication strength

References

External resources and documentation

Similar Rules

Explore related security rules for Python

Frequently Asked Questions

Common questions about Django Empty Password in set_password()

set_password('') hashes the empty string and stores it as a valid password hash. Any authentication attempt with an empty password field will succeed against this hash. set_unusable_password() stores '!' as the password hash, which never matches any real password string. Django's authentication backend explicitly checks for unusable passwords and returns False for any authenticate() call, even with an empty password argument.
Yes. The common pattern request.POST.get('password', '') passes an empty string default. If this value is used directly in set_password() without checking whether the field was actually submitted, and if the form is submitted without a password value, the account receives an empty password. Always use None as the default and check for None before calling set_password().
Only if validate_password() is called explicitly before set_password(). Django's MinimumLengthValidator (minimum 8 characters) raises a ValidationError for empty strings. However, validate_password() is not called automatically by set_password() -- it must be called explicitly in the view. This is why the bug can occur even in applications that have password validators configured in AUTH_PASSWORD_VALIDATORS.
No. In tests, use User.objects.create_user(username='test', password='testpass123') which creates an account with a known password for testing. For testing scenarios where you need an unusable password, use user.set_unusable_password(). Never use set_password('') even in tests, as it can propagate to production if test helpers are reused in deployment scripts.
Django's AbstractBaseUser.has_usable_password() returns False if the password field starts with '!'. set_unusable_password() stores UNUSABLE_PASSWORD_PREFIX + UNUSABLE_PASSWORD_SUFFIX as the hash. Django's check_password() and authenticate() use has_usable_password() to short-circuit before checking the hash, ensuring accounts with unusable passwords can never be authenticated via password.
Run a database query to find all accounts where the password hash matches the hash of an empty string (Python: check(make_password('')) against stored hashes). Force password resets for all such accounts immediately, or call set_unusable_password() on them and require them to go through a password creation flow on next login.

New feature

Get these findings posted directly on your GitHub pull requests

The Django Empty Password in set_password() rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.

See how it works