diff --git a/authentik/lib/tests/test_http.py b/authentik/lib/tests/test_http.py index 4fa8ab619631..910993db493e 100644 --- a/authentik/lib/tests/test_http.py +++ b/authentik/lib/tests/test_http.py @@ -30,6 +30,11 @@ def test_forward_for(self): request = self.factory.get("/", HTTP_X_FORWARDED_FOR="127.0.0.2") self.assertEqual(ClientIPMiddleware.get_client_ip(request), "127.0.0.2") + def test_forward_for_invalid(self): + """Test invalid forward for""" + request = self.factory.get("/", HTTP_X_FORWARDED_FOR="foobar") + self.assertEqual(ClientIPMiddleware.get_client_ip(request), ClientIPMiddleware.default_ip) + def test_fake_outpost(self): """Test faked IP which is overridden by an outpost""" token = Token.objects.create( @@ -53,6 +58,17 @@ def test_fake_outpost(self): }, ) self.assertEqual(ClientIPMiddleware.get_client_ip(request), "127.0.0.1") + # Invalid, not a real IP + self.user.type = UserTypes.INTERNAL_SERVICE_ACCOUNT + self.user.save() + request = self.factory.get( + "/", + **{ + ClientIPMiddleware.outpost_remote_ip_header: "foobar", + ClientIPMiddleware.outpost_token_header: token.key, + }, + ) + self.assertEqual(ClientIPMiddleware.get_client_ip(request), "127.0.0.1") # Valid self.user.type = UserTypes.INTERNAL_SERVICE_ACCOUNT self.user.save() diff --git a/authentik/root/middleware.py b/authentik/root/middleware.py index 017dee60c6dd..e1a77116a57f 100644 --- a/authentik/root/middleware.py +++ b/authentik/root/middleware.py @@ -2,6 +2,7 @@ from collections.abc import Callable from hashlib import sha512 +from ipaddress import ip_address from time import perf_counter, time from typing import Any @@ -174,6 +175,7 @@ class ClientIPMiddleware: def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]): self.get_response = get_response + self.logger = get_logger().bind() def _get_client_ip_from_meta(self, meta: dict[str, Any]) -> str: """Attempt to get the client's IP by checking common HTTP Headers. @@ -185,11 +187,16 @@ def _get_client_ip_from_meta(self, meta: dict[str, Any]) -> str: "HTTP_X_FORWARDED_FOR", "REMOTE_ADDR", ) - for _header in headers: - if _header in meta: - ips: list[str] = meta.get(_header).split(",") - return ips[0].strip() - return self.default_ip + try: + for _header in headers: + if _header in meta: + ips: list[str] = meta.get(_header).split(",") + # Ensure the IP parses as a valid IP + return str(ip_address(ips[0].strip())) + return self.default_ip + except ValueError as exc: + self.logger.debug("Invalid remote IP", exc=exc) + return self.default_ip # FIXME: this should probably not be in `root` but rather in a middleware in `outposts` # but for now it's fine @@ -228,7 +235,11 @@ def _get_outpost_override_ip(self, request: HttpRequest) -> str | None: Hub.current.scope.set_user(user) # Set the outpost service account on the request setattr(request, self.request_attr_outpost_user, user) - return delegated_ip + try: + return str(ip_address(delegated_ip)) + except ValueError as exc: + self.logger.debug("Invalid remote IP from Outpost", exc=exc) + return None def _get_client_ip(self, request: HttpRequest | None) -> str: """Attempt to get the client's IP by checking common HTTP Headers. diff --git a/blueprints/default/flow-default-authentication-flow.yaml b/blueprints/default/flow-default-authentication-flow.yaml index 123c4e5a7aca..a7a7e3e16a17 100644 --- a/blueprints/default/flow-default-authentication-flow.yaml +++ b/blueprints/default/flow-default-authentication-flow.yaml @@ -82,3 +82,5 @@ entries: order: 10 target: !KeyOf default-authentication-flow-password-binding policy: !KeyOf default-authentication-flow-password-optional + attrs: + failure_result: true diff --git a/website/docs/security/CVE-2024-42490.md b/website/docs/security/CVE-2024-42490.md index 3a024aa80d90..f32c98e7f6bf 100644 --- a/website/docs/security/CVE-2024-42490.md +++ b/website/docs/security/CVE-2024-42490.md @@ -2,7 +2,7 @@ _Reported by [@m2a2](https://github.com/m2a2)_ -## Improper Authorization for Token modification +## Insufficient Authorization for several API endpoints ### Summary diff --git a/website/docs/security/CVE-2024-47070.md b/website/docs/security/CVE-2024-47070.md new file mode 100644 index 000000000000..179f6c62f2ad --- /dev/null +++ b/website/docs/security/CVE-2024-47070.md @@ -0,0 +1,35 @@ +# CVE-2024-47070 + +_Reported by [@efpi-bot](https://github.com/efpi-bot) from [LogicalTrust](https://logicaltrust.net/en/)_ + +## Password authentication bypass via X-Forwarded-For HTTP header + +### Summary + +The vulnerability allows bypassing policies by adding X-Forwarded-For header with unparsable IP address, e.g. "a". This results in a possibility to authenticate/authorize to any account with known login or email address. + +Since the default authentication flow uses a policy to enable the password stage only when there is no password stage selected on the Identification stage, this vulnerability can be used to skip this policy and continue without the password stage. + +### Am I affected + +This can be exploited for the following configurations: + +- An attacker can access authentik without a reverse proxy (and `AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS` is not configured properly) +- The reverse proxy configuration does not correctly overwrite X-Forwarded-For +- Policies (User and group bindings do _not_ apply) are bound to authentication/authorization flows + +### Patches + +authentik 2024.6.5 and 2024.8.3 fix this issue. + +### Workarounds + +Ensure the X-Forwarded-For header is always set by the reverse proxy, and is always set to a correct IP. + +In addition you can manually change the _Failure result_ option on policy bindings to _Pass_, which will prevent any stages from being skipped if a malicious request is received. + +### For more information + +If you have any questions or comments about this advisory: + +- Email us at [security@goauthentik.io](mailto:security@goauthentik.io) diff --git a/website/sidebars.js b/website/sidebars.js index c7f84756b4c8..287dafaa1ede 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -512,6 +512,7 @@ const docsSidebar = { "security/security-hardening", "security/policy", "security/CVE-2024-47077", + "security/CVE-2024-47070", "security/CVE-2024-42490", "security/CVE-2024-38371", "security/CVE-2024-37905",