Skip to content

Commit 8d26cfb

Browse files
1 parent 0d35ded commit 8d26cfb

1 file changed

Lines changed: 61 additions & 0 deletions

File tree

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-cc7c-9jff-58wj",
4+
"modified": "2026-09-16T13:55:48Z",
5+
"published": "2026-09-16T13:55:48Z",
6+
"aliases": [
7+
"CVE-2026-61598"
8+
],
9+
"summary": "djust: Client mass-assignment of arbitrary view attributes via the default dj-model update_model handler",
10+
"details": "### Impact\n`djust.mixins.model_binding.ModelBindingMixin` provides a default `update_model` event handler and is part of the **LiveView base MRO**, so every LiveView exposes it. It `setattr`s a view attribute whose **name is client-supplied** (`field`), gated only by: reject `_`-prefixed names; reject a **14-entry denylist** of framework internals (`FORBIDDEN_MODEL_FIELDS`); optional `allowed_model_fields` which **defaults to None = allow all**; and `hasattr` existence.\n\nResult: a client can set **any public, existing view attribute — not just the fields actually bound with `dj-model=` in the rendered template**. The denylist covers framework plumbing but nothing about developer business/authz state, and the allowlist is opt-in (off by default). A developer who binds one `dj-model=\"search\"` input and also keeps `self.account_id` / `self.is_admin` / `self.total_price` as view state does not realize a client can set ALL of them via `{type:event, event:\"update_model\", params:{field, value}}` over the WebSocket. Type coercion matches the target attribute's type (so `\"true\"` -> bool True), aiding the attacker.\n\n**Severity High** for apps that hold authorization/ownership/business state in public view attributes (the normal djust pattern) -> state tampering / IDOR / authz-flag manipulation; Low otherwise. Default-on across every LiveView. For a public (no-login) view an anonymous client can mass-assign; for an authenticated view a logged-in user can tamper their own session's view state (the IDOR/authz vector when downstream handlers act on it without re-authorizing).\n\nReproduced: a view with `account_id/is_admin/total_price` (none bound with dj-model) had all three set via `update_model` calls.\n\n### Patches\nRestrict the default handler to fields actually exposed via `dj-model=`: have the template renderer record the bound-field set per render and reject any `field` outside it (preferred, secure + zero-config); or make `allowed_model_fields` fail-closed (required). Keep `FORBIDDEN_MODEL_FIELDS` only as defense-in-depth. Add a regression that a non-`dj-model` public attribute (e.g. `is_admin`) is rejected while a bound field still updates.\n\n### Workarounds\nSet `allowed_model_fields` explicitly on every view using dj-model (or subclassing LiveView) to the minimal list of bindable fields; do not keep authorization/ownership state in public view attributes that share the view with dj-model bindings.\n\n### References\nReproducer + finding writeup retained privately by the maintainer.",
11+
"severity": [
12+
{
13+
"type": "CVSS_V4",
14+
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N"
15+
}
16+
],
17+
"affected": [
18+
{
19+
"package": {
20+
"ecosystem": "PyPI",
21+
"name": "djust"
22+
},
23+
"ranges": [
24+
{
25+
"type": "ECOSYSTEM",
26+
"events": [
27+
{
28+
"introduced": "0"
29+
},
30+
{
31+
"fixed": "1.0.7"
32+
}
33+
]
34+
}
35+
]
36+
}
37+
],
38+
"references": [
39+
{
40+
"type": "WEB",
41+
"url": "https://github.com/djust-org/djust/security/advisories/GHSA-cc7c-9jff-58wj"
42+
},
43+
{
44+
"type": "PACKAGE",
45+
"url": "https://github.com/djust-org/djust"
46+
},
47+
{
48+
"type": "WEB",
49+
"url": "https://github.com/djust-org/djust/releases/tag/v1.0.7"
50+
}
51+
],
52+
"database_specific": {
53+
"cwe_ids": [
54+
"CWE-915"
55+
],
56+
"severity": "HIGH",
57+
"github_reviewed": true,
58+
"github_reviewed_at": "2026-09-16T13:55:48Z",
59+
"nvd_published_at": null
60+
}
61+
}

0 commit comments

Comments
 (0)