Skip to content

Commit c3629d1

Browse files
committed
security: fix CVE-2025-53942 (#15719)
Signed-off-by: Jens Langhammer <[email protected]> # Conflicts: # authentik/stages/user_login/stage.py # authentik/stages/user_login/tests.py # website/docs/sidebar.mjs
1 parent 58f82a0 commit c3629d1

File tree

6 files changed

+85
-3
lines changed

6 files changed

+85
-3
lines changed

authentik/core/middleware.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from functools import partial
66
from uuid import uuid4
77

8+
from django.contrib.auth import logout
89
from django.contrib.auth.models import AnonymousUser
910
from django.core.exceptions import ImproperlyConfigured
1011
from django.http import HttpRequest, HttpResponse
@@ -58,6 +59,11 @@ def process_request(self, request):
5859
request.user = SimpleLazyObject(lambda: get_user(request))
5960
request.auser = partial(aget_user, request)
6061

62+
user = request.user
63+
if user and user.is_authenticated and not user.is_active:
64+
logout(request)
65+
raise AssertionError()
66+
6167

6268
class ImpersonateMiddleware:
6369
"""Middleware to impersonate users"""

authentik/stages/password/tests.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,29 @@ def test_valid_password(self):
8989
self.assertEqual(response.status_code, 200)
9090
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
9191

92+
def test_valid_password_inactive(self):
93+
"""Test with a valid pending user and valid password"""
94+
self.user.is_active = False
95+
self.user.save()
96+
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
97+
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
98+
session = self.client.session
99+
session[SESSION_KEY_PLAN] = plan
100+
session.save()
101+
102+
response = self.client.post(
103+
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
104+
# Form data
105+
{"password": self.user.username},
106+
)
107+
108+
self.assertEqual(response.status_code, 200)
109+
self.assertStageResponse(
110+
response,
111+
self.flow,
112+
response_errors={"password": [{"string": "Invalid password", "code": "invalid"}]},
113+
)
114+
92115
def test_invalid_password(self):
93116
"""Test with a valid pending user and invalid password"""
94117
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])

authentik/stages/user_login/stage.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ def do_login(self, request: HttpRequest, remember: bool = False) -> HttpResponse
9191
user: User = self.executor.plan.context[PLAN_CONTEXT_PENDING_USER]
9292
if not user.is_active:
9393
self.logger.warning("User is not active, login will not work.")
94+
return self.executor.stage_invalid()
9495
delta = self.set_session_duration(remember)
9596
self.set_session_ip()
9697
# the `user_logged_in` signal will update the user to write the `last_login` field

authentik/stages/user_login/tests.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from django.urls import reverse
77
from django.utils.timezone import now
88

9+
from authentik.blueprints.tests import apply_blueprint
910
from authentik.core.models import AuthenticatedSession, Session
1011
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
1112
from authentik.flows.markers import StageMarker
@@ -174,6 +175,7 @@ def test_without_user(self):
174175
component="ak-stage-access-denied",
175176
)
176177

178+
@apply_blueprint("default/flow-default-user-settings-flow.yaml")
177179
def test_inactive_account(self):
178180
"""Test with a valid pending user and backend"""
179181
self.user.is_active = False
@@ -187,8 +189,25 @@ def test_inactive_account(self):
187189
response = self.client.get(
188190
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
189191
)
190-
191192
self.assertEqual(response.status_code, 200)
192-
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
193+
self.assertStageResponse(
194+
response, self.flow, component="ak-stage-access-denied", error_message="Unknown error"
195+
)
196+
197+
# Check that API requests get rejected
193198
response = self.client.get(reverse("authentik_api:application-list"))
194199
self.assertEqual(response.status_code, 403)
200+
201+
# Check that flow requests requiring a user also get rejected
202+
response = self.client.get(
203+
reverse(
204+
"authentik_api:flow-executor",
205+
kwargs={"flow_slug": "default-user-settings-flow"},
206+
)
207+
)
208+
self.assertStageResponse(
209+
response,
210+
self.flow,
211+
component="ak-stage-access-denied",
212+
error_message="Flow does not apply to current user.",
213+
)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# CVE-2025-53942
2+
3+
_Reported by [@pascalwei](https://github.com/pascalwei)_
4+
5+
## Insufficient check for account active status when authenticating with OAuth/SAML Sources
6+
7+
### Summary
8+
9+
Deactivated users that had either enrolled via OAuth/SAML or had their account connected to an OAuth/SAML account can still partially access authentik even if their account is deactivated. They end up in a half-authenticated state where they cannot access the API but crucially they can authorize applications if they know the URL of the application.
10+
11+
### Patches
12+
13+
authentik 2025.4.4 and 2025.6.4 fix this issue.
14+
15+
### Workarounds
16+
17+
Adding an expression policy to the user login stage on the respective authentication flow with the expression of
18+
19+
```py
20+
return request.context["pending_user"].is_active
21+
```
22+
23+
This expression will only activate the user login stage when the user is active.
24+
25+
### For more information
26+
27+
If you have any questions or comments about this advisory:
28+
29+
- Email us at [[email protected]](mailto:[email protected]).

website/sidebars.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,11 @@ export default {
716716
{
717717
type: "category",
718718
label: "2025",
719-
items: ["security/cves/CVE-2025-52553", "security/cves/CVE-2025-29928"],
719+
items: [
720+
"security/cves/CVE-2025-53942",
721+
"security/cves/CVE-2025-52553",
722+
"security/cves/CVE-2025-29928",
723+
],
720724
},
721725
{
722726
type: "category",

0 commit comments

Comments
 (0)