high · 8.2CVE-2026-55571Aug 25, 2026

CVE-2026-55571: djust LiveView WebSocket Authentication Bypass

Rohit Hatagale
AI Security Researcher, SecureLayer7

A flaw in djust's LiveView WebSocket consumer lets an unauthenticated attacker skip a login redirect and call protected server-side event handlers, bypassing all access controls on gated views.

Packagedjust
Ecosystempip
Affected< 1.0.4
Fixed in1.0.4
CVE-2026-55571: djust LiveView WebSocket Authentication Bypass

The problem

djust's LiveViewConsumer mounts LiveViews over a persistent WebSocket. When a view is protected by login_required, permission_required, or a redirecting on_mount hook, the consumer sent the client a {"type":"navigate"} redirect frame and returned early, but it did NOT close the socket and did NOT clear self.view_instance.

A real browser obeys the navigate frame and disconnects. A raw WebSocket client that ignores the redirect keeps a live, fully mounted socket. Because handle_event never re-checked auth after mount, that client could then send {"type":"event"} frames and invoke any @event_handler on the gated view with zero authentication.

Only the PermissionDenied branch correctly called close(4403). The auth-redirect and on-mount-redirect branches did not.

Proof of concept

A working proof-of-concept for CVE-2026-55571 in djust, with the exact payload below.

python
# 1. Connect as anonymous over WebSocket to a login_required view
# Use Django Channels' WebsocketCommunicator or any raw WS client

# Step 1 — mount the gated view (server replies with navigate frame, but socket stays open)
connect_msg = {
    "type": "mount",
    "path": "/dashboard/"   # any login_required LiveView path
}

# Step 2 — server sends redirect but does NOT close the socket:
# {"type": "navigate", "to": "/accounts/login/?next=/dashboard/"}
# Ignore it. Socket is still open and self.view_instance is still set.

# Step 3 — send an event frame to invoke a protected handler
event_msg = {
    "type": "event",
    "handler": "delete_record",   # any @event_handler on the gated view
    "payload": {"id": 1}
}

# Handler executes with no authenticated session on djust < 1.0.4

The root cause is a missing close() call after the auth-redirect branch in handle_mount. The PermissionDenied branch already called close(code=4403) and cleared self.view_instance, but the login_required / permission_required / on_mount-redirect branches only sent the navigate frame and returned, leaving the socket alive with the view fully instantiated.

Because handle_event trusted that a mounted socket implied an authorized session, it routed incoming {"type":"event"} frames directly to the decorated handler with no second auth check. The patch (commit 1ae8aa9, PR #1780) makes all redirect-triggering branches call close(code=4403) and clear self.view_instance immediately after sending the navigate frame, matching the behavior of the existing PermissionDenied path.

The optional LIVEVIEW_CONFIG['reauth_on_event'] = True setting added in 1.0.4 provides defense-in-depth by re-resolving the user from the session and re-running the view's auth check on every event dispatch for gated views (default off).

The fix

Upgrade to **djust 1.0.4** (commit 1ae8aa9, PR #1780). If an immediate upgrade is not possible, add an explicit auth check at the top of every @event_handler on gated views (request.user.is_authenticated / permission check), or override handle_mount to call await self.close(code=4403) after any auth redirect.

After upgrading, consider enabling LIVEVIEW_CONFIG['reauth_on_event'] = True for defense-in-depth on sensitive views.

Reporter not attributed.

References: [1][2][3][4]

Related research