Skip to main content
Vulnerability Database/CVE-2026-87975

CVE-2026-87975: Django Authentication Bypass Vulnerability

CVE-2026-87975 is an authentication bypass flaw in Django that allows authenticated users to delete objects outside their permitted queryset without proper authorization. This article covers technical details, affected versions, impact, and mitigation strategies.

Published:

CVE-2026-87975 Overview

CVE-2026-87975 is a broken access control vulnerability in the Django web framework. The flaw resides in django.forms.models.BaseModelFormSet.save_existing_objects(), which incorrectly trusted the presence of a primary key on a submitted form's instance as proof that the instance belonged to the formset's limiting queryset. An authenticated user permitted to submit a vulnerable formset can forge management-form data to delete rows outside the formset's queryset without any permission on the targeted object. The issue affects Django 6.1 before 6.1.2, 6.0 before 6.0.9, and 5.2 before 5.2.18. Models using the default AutoField or BigAutoField primary key are not affected.

Critical Impact

Authenticated users can delete arbitrary model instances outside their authorized queryset when formsets use editable primary keys such as OneToOneField, natural keys, or UUID fields.

Affected Products

  • Django 6.1 before 6.1.2
  • Django 6.0 before 6.0.9
  • Django 5.2 before 5.2.18 (earlier unsupported series such as 5.1.x, 5.0.x, and 4.2.x may also be affected)

Discovery Timeline

  • 2026-10-06 - CVE CVE-2026-87975 published to NVD
  • 2026-10-06 - Django releases security patches and advisory
  • 2026-10-06 - Last updated in NVD database
  • Vulnerability reported to Django by Seonggwon Yoon

Technical Details for CVE-2026-87975

Vulnerability Analysis

The vulnerability is an Authorization Bypass [CWE-639] in Django's model formset processing. When Django processes a submitted model formset, BaseModelFormSet.save_existing_objects() iterates over initial_forms to determine which existing database rows should be deleted. The pre-patch logic skipped the delete path only when obj._is_pk_set() returned False.

This check conflated two different states: an instance with no primary key, and an instance whose primary key was populated from user-submitted POST data but which was never fetched from the database. When the model's primary key is a field accepted by the form, attacker-controlled POST data can populate the primary key of a freshly constructed instance that lives outside the formset's limiting queryset.

The attacker then marks that forged form for deletion through management-form fields, and Django issues a DELETE against the targeted row without evaluating whether the current user has permission on that object.

Root Cause

The root cause is an incorrect trust boundary. Django assumed that any form instance with a set primary key had been loaded through the formset's queryset. In reality, the instance is reconstructed from form input, and its state flag _state.adding remains True because the row was never fetched. Models that rely on the default AutoField or BigAutoField primary key are not affected, because the auto-generated numeric primary key is not exposed as a writable form field.

Attack Vector

Exploitation requires an authenticated user with permission to submit a model formset whose underlying model uses a user-settable primary key, such as a OneToOneField (including the parent link used as the primary key of an inline formset's model), a natural key, or a UUID primary key included in the form's fields. The attacker crafts forged management-form data referencing an out-of-queryset primary key and marks the corresponding form for deletion. On submission, Django deletes the targeted row.

python
        forms_to_delete = self.deleted_forms
        for form in self.initial_forms:
            obj = form.instance
-            # If the pk is None, it means either:
-            # 1. The object is an unexpected empty model, created by invalid
-            #    POST data such as an object outside the formset's queryset.
+            # If the pk is unset or the instance is being added, it could mean:
+            # 1. The object is an unexpected unfetched instance, created by
+            #    invalid POST data such as an object outside the formset's
+            #    queryset, or a formset with edit_only=True.
            # 2. The object was already deleted from the database.
-            if not obj._is_pk_set():
+            if not obj._is_pk_set() or obj._state.adding:
                continue
            if form in forms_to_delete:
                self.deleted_objects.append(obj)

Source: Django Commit 83cbd21. The patch adds an obj._state.adding check so Django skips any instance that was never fetched from the database, closing the forgery path.

Detect Identity Attacks the Moment Authentication Fails

Singularity™ Identity surfaces credential misuse, lateral movement, and privilege escalation in real time, so you can contain the breach before it spreads.

Stop Identity Attacks

Detection Methods for CVE-2026-87975

Indicators of Compromise

  • Unexpected DELETE statements in database audit logs that target primary keys the submitting user should not be able to reach through the UI.
  • Formset POST requests whose management-form *-INITIAL_FORMS count or per-form pk fields reference identifiers inconsistent with the server-side queryset for that user.
  • Deletion of rows containing OneToOneField, natural, or UUID primary keys without corresponding authorized workflow activity.

Detection Strategies

  • Enable Django request logging and compare submitted formset primary keys against the queryset returned to the user for the same endpoint and session.
  • Instrument post_delete signals on sensitive models to record the acting user, source IP, and request path for every deletion.
  • Review web server access logs for POST requests to formset endpoints containing primary-key values the authenticated user has no legitimate access to.

Monitoring Recommendations

  • Alert on bursts of deletions targeting models with OneToOneField, natural, or UUID primary keys, especially those exposed through inline or admin formsets.
  • Correlate authentication telemetry with model deletion events to identify low-privilege accounts performing high-privilege operations.
  • Forward Django application logs and database audit trails into a centralized analytics backend so cross-request forgery patterns are visible to the SOC.

How to Mitigate CVE-2026-87975

Immediate Actions Required

  • Upgrade Django to 6.1.2, 6.0.9, or 5.2.18 depending on the deployed series.
  • Inventory applications for model formsets whose models use OneToOneField, natural, or UUID primary keys and prioritize those endpoints for patching.
  • Audit recent deletions on affected models to confirm no unauthorized rows were removed before the patch was applied.

Patch Information

The Django project released fixes on 2026-10-06 in versions 6.1.2, 6.0.9, and 5.2.18. The core change in django/forms/models.py adds a check for obj._state.adding so unfetched instances constructed from POST data are no longer treated as formset members. Patch references: Django Commit 83cbd21, Django Commit b83ab92, Django Commit cce6c57, and Django Commit ff27883. Full details are available in the Django Weblog Security Releases.

Workarounds

  • Remove user-settable primary key fields from the form's fields list on affected formsets so attackers cannot populate the primary key through POST data.
  • Enforce object-level permission checks in save_existing_objects() overrides, or validate each submitted pk against the formset's limiting queryset before saving.
  • Disable deletion on exposed formsets by setting can_delete=False where business logic allows.
bash
# Upgrade Django to a patched release
pip install --upgrade "Django>=5.2.18,<5.3"   # for 5.2.x deployments
pip install --upgrade "Django>=6.0.9,<6.1"    # for 6.0.x deployments
pip install --upgrade "Django>=6.1.2,<6.2"    # for 6.1.x deployments

# Verify the installed version
python -m django --version

Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

Experience the Most Advanced Cybersecurity Platform

See how the world’s most intelligent, autonomous cybersecurity platform can protect your organization today and into the future.