Broken Object Property Level Authorization

Broken Object Property Level Authorization in Django [CVE-2007-0405]

[Updated Mar 2026] Updated CVE-2007-0405

Overview

CVE-2007-0405 describes a bug in Django 0.95 where the LazyUser class used by AuthenticationMiddleware cached the user name across requests. This could allow a remote authenticated user to operate with the privileges of a different user if session data or caches were manipulated, effectively bypassing object-level checks. The vulnerability manifests when identity is not re-resolved per request and a cached username can be stale or manipulated, causing authorization decisions to rely on an outdated or attacker-controlled value. This pattern is a classic Broken Object Property Level Authorization risk because the authorization decision depends on a cached property rather than a fresh, per-request lookup. Patch availability and later Django versions fix this by ensuring per-request identity resolution and avoiding cross-request caches. In practice, an attacker with a valid session could manipulate session data to influence the identity used by subsequent requests if the middleware caches and reuses a previous username across requests. Because request.user may be constructed from a cached LazyUser instance, the server could treat the current request as belonging to another user, granting access to resources the attacker should not reach. The safe approach is to perform per-request user resolution and avoid caching user identity across requests, which modern Django authentication middleware handles automatically. Remediation guidance shows replacing this cross-request caching with per-request resolution, upgrading Django, and adopting the framework's recommended authentication flow. The example below demonstrates vulnerable and fixed patterns side-by-side in real Django code and explains how to fix them in a concrete way that preserves correct authorization semantics. Finally, implement robust object-level authorization checks (permissions, is_staff, has_perm) and add tests to verify that changes in session data do not alter the effective user identity mid-request. This aligns with modern Django security practices and prevents CVE-2007-0405-style issues from reappearing.

Affected Versions

Django 0.95 (and earlier; the issue described by CVE-2007-0405 is addressed in later releases)

Code Fix Example

Django API Security Remediation
# Vulnerable pattern
class LazyUser(object):
    def __init__(self, user_id, cached_username=None):
        self.user_id = user_id
        self._cached_username = cached_username

    @property
    def username(self):
        if self._cached_username is None:
            from django.contrib.auth.models import User
            user = User.objects.get(pk=self.user_id)
            self._cached_username = user.username
        return self._cached_username


def simple_mw(get_response):
    def middleware(request):
        if 'user_id' in request.session:
            request.user = LazyUser(request.session['user_id'], request.session.get('username'))
        else:
            from django.contrib.auth.models import AnonymousUser
            request.user = AnonymousUser()
        return get_response(request)
    return middleware

# Exploit: Changing request.session['username'] in between requests will not reflect in request.user due to caching.

# Fixed pattern
class LazyUserFixed(object):
    def __init__(self, user_id):
        self.user_id = user_id

    @property
    def username(self):
        from django.contrib.auth import get_user_model
        User = get_user_model()
        user = User.objects.get(pk=self.user_id)
        return user.username


def fixed_mw(get_response):
    def middleware(request):
        if 'user_id' in request.session:
            request.user = LazyUserFixed(request.session['user_id'])
        else:
            from django.contrib.auth.models import AnonymousUser
            request.user = AnonymousUser()
        return get_response(request)
    return middleware

CVE References

Choose which optional cookies to allow. You can change this any time.