CVE-2026-61599
Unauthenticated client imports arbitrary modules via view path, code exec.
- CVSS 8.8
- 470
- Input Validation and Sanitization
- Remote
djust provides Phoenix LiveView-style reactive server-side rendering for Django with Rust-powered performance. Prior to version 1.0.7, the djust live transport resolves the LiveView to mount from a client-supplied dotted path by calling `__import__(module_path, …)`. The module is imported — running its top-level code (import side effects) — before the framework checks that the resolved object is a `LiveView` subclass and before any per-view authentication. The `LIVEVIEW_ALLOWED_MODULES` allowlist that should contain this is fail-open (`if allowed_modules:` — skipped when the setting is unset, the framework default) and uses loose `startswith` matching. An unauthenticated WebSocket client (the WS handshake does not require auth; per-view auth runs only after import + instantiate) can therefore send a `mount` / `live_redirect_mount` / `url_change` frame (or an SSE mount) with `view = "<any.importable.module>.AnyName"` and cause the server to import — and execute the top-level code of — any importable Python module by name. Version 1.0.7 fixes the issue with a fail-closed resolution gate (`djust._view_resolution.is_view_import_allowed`): a client view path resolves only if (a) its module is already loaded (`sys.modules` — so resolving runs no new code; URL-routed views loaded by URLconf at startup keep working with zero config) or (b) it matches `LIVEVIEW_ALLOWED_MODULES` on a module-segment boundary (explicit opt-in for lazily-imported views). The gate runs before `__import__` at all three sinks (+ defense-in-depth inside `_instantiate_view`). As a workaround, set `LIVEVIEW_ALLOWED_MODULES` to the narrow list of modules that contain your mountable LiveView classes. (Note: pre-patch the allowlist is `startswith`-matched and the import still precedes the subclass check, so this is mitigation, not a complete fix.)
- CWE
- 470
- CVSS base score
- 8.8
- Published
- 2026-09-16
- OWASP
- A04 Insecure Design
- Orthogonal defect classification
- Checking
- Code defect classification
- Incorrect Check
- Category
- Input Validation and Sanitization
- Subcategory
- Command Injection
- Accessibility scope
- Remote
- Impact
- Arbitrary Code Execution
- Affected component
- djust
- Fixed by upgrading
- Yes
Solution
Upgrade djust to version 1.0.7 or later.
Vulnerable code sample
import sys
from typing import Type
# Placeholder for the framework's LiveView base class
class LiveView:
pass
# Framework setting (default fail‑open)
LIVEVIEW_ALLOWED_MODULES = [] # e.g. ["myapp.views"]
def resolve_live_view(view_path: str) -> Type[LiveView]:
"""
Resolve a dotted ``module.Class`` string supplied by a client into a LiveView subclass.
"""
# VULNERABLE: imports untrusted module before any validation
module_path, _, class_name = view_path.rpartition('.')
module = __import__(module_path, fromlist=[class_name])
view_cls = getattr(module, class_name)
# Allowlist is checked *after* the import and uses a lax startswith
if LIVEVIEW_ALLOWED_MODULES:
if not any(module_path.startswith(allowed) for allowed in LIVEVIEW_ALLOWED_MODULES):
raise PermissionError("Module not allowed")
if not issubclass(view_cls, LiveView):
raise TypeError("Resolved class is not a LiveView")
return view_clsPatched code sample
import sys
from typing import Type
# Placeholder for the framework's LiveView base class
class LiveView:
pass
# Framework setting (default fail‑open)
LIVEVIEW_ALLOWED_MODULES = [] # e.g. ["myapp.views"]
def is_view_import_allowed(module_path: str) -> bool:
"""
Return True if the module is permitted for lazy import.
"""
# Fail‑closed: reject when allowlist is empty
if not LIVEVIEW_ALLOWED_MODULES:
return False
# Ensure match occurs on a segment boundary
return any(module_path == allowed or module_path.startswith(f"{allowed}.") for allowed in LIVEVIEW_ALLOWED_MODULES)
def resolve_live_view(view_path: str) -> Type[LiveView]:
"""
Resolve a dotted ``module.Class`` string supplied by a client into a LiveView subclass.
"""
# FIX: validate module before importing any code
module_path, _, class_name = view_path.rpartition('.')
if not is_view_import_allowed(module_path):
raise PermissionError("Module not allowed")
# Import only if the module hasn't been loaded already (no new code execution)
if module_path in sys.modules:
module = sys.modules[module_path]
else:
module = __import__(module_path, fromlist=[class_name])
view_cls = getattr(module, class_name)
if not issubclass(view_cls, LiveView):
raise TypeError("Resolved class is not a LiveView")
return view_clsPayload
__VAITP_MODEL_REFUSED__
Cite this entry
@misc{vaitp:cve202661599,
title = {{Unauthenticated client imports arbitrary modules via view path, code exec.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-61599},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-61599/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
