From 897252f9cd73757bc1e6ab25f44940978f542c0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Z=C3=B6ller?= Date: Tue, 7 Apr 2026 12:49:46 +0200 Subject: [PATCH] Finalizing Security Optimizations --- .../CustomAuthenticationEntryPoint.java | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/javahippie/fitpub/security/CustomAuthenticationEntryPoint.java b/src/main/java/net/javahippie/fitpub/security/CustomAuthenticationEntryPoint.java index 18ae899..480eb58 100644 --- a/src/main/java/net/javahippie/fitpub/security/CustomAuthenticationEntryPoint.java +++ b/src/main/java/net/javahippie/fitpub/security/CustomAuthenticationEntryPoint.java @@ -9,6 +9,8 @@ import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; /** * Custom authentication entry point that handles unauthenticated requests. @@ -45,9 +47,42 @@ public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint return; } - // HTML page requests should redirect to login + // HTML page requests should redirect to login. The redirect parameter is built + // from the original request URI so the login page can later send the user back. + // We must restrict it to a single internal path — anything starting with "//" or + // "/\" could be interpreted by browsers as a protocol-relative URL pointing at + // an external host, turning this into an open redirect. log.debug("HTML page request - redirecting to /login"); - String redirectUrl = "/login?redirect=" + requestUri; + String redirectUrl; + if (isSafeInternalPath(requestUri)) { + redirectUrl = "/login?redirect=" + URLEncoder.encode(requestUri, StandardCharsets.UTF_8); + } else { + log.warn("Refusing to propagate suspicious redirect target: {}", requestUri); + redirectUrl = "/login"; + } response.sendRedirect(redirectUrl); } + + /** + * Returns true if the given path is a safe single-slash internal path that can be + * round-tripped through a {@code redirect} query parameter without enabling an open + * redirect. Rejects null/empty, anything not starting with {@code /}, and anything + * starting with {@code //} or {@code /\} (both of which browsers may interpret as + * protocol-relative URLs to a different host). + */ + private static boolean isSafeInternalPath(String path) { + if (path == null || path.isEmpty()) { + return false; + } + if (path.charAt(0) != '/') { + return false; + } + if (path.length() >= 2) { + char second = path.charAt(1); + if (second == '/' || second == '\\') { + return false; + } + } + return true; + } }