This is an automated email from the ASF dual-hosted git repository. lprimak pushed a commit to branch 3.x in repository https://gitbox.apache.org/repos/asf/shiro.git
commit bd278bcc360938161abb7c4740c8e1c4a1e44831 Author: lprimak <[email protected]> AuthorDate: Thu May 21 19:34:39 2026 -0500 enh(jakarta-ee): strip out the host part of the referer header --- .../shiro/ee/filters/FormResubmitSupport.java | 28 +++- .../apache/shiro/ee/filters/FormSupportTest.java | 143 +++++++++++++++++++-- 2 files changed, 153 insertions(+), 18 deletions(-) diff --git a/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitSupport.java b/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitSupport.java index 0e4732eff..b3aee2bed 100644 --- a/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitSupport.java +++ b/support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitSupport.java @@ -259,13 +259,31 @@ public class FormResubmitSupport { static String getReferer(HttpServletRequest request) { String referer = request.getHeader("referer"); - if (referer != null) { - // do not switch to https if custom port is specified - if (!referer.matches("^http:\\/\\/[A-z|.|[0-9]]+:[0-9]+(\\/.*|$)")) { - referer = referer.replaceFirst("^http:", "https:"); + if (referer == null || referer.isBlank()) { + return null; + } + + try { + URI uri = URI.create(referer); + + String contextPath = WebUtils.getContextPath(request); + String path = WebUtils.normalize(uri.getPath()); + + if (path == null) { + return null; } + + if (!contextPath.isEmpty() + && !path.equals(contextPath) + && !path.startsWith(contextPath + "/")) { + return null; + } + + String query = uri.getRawQuery(); + return query == null ? path : path + "?" + query; + } catch (IllegalArgumentException e) { + return null; } - return referer; } /** diff --git a/support/jakarta-ee/src/test/java/org/apache/shiro/ee/filters/FormSupportTest.java b/support/jakarta-ee/src/test/java/org/apache/shiro/ee/filters/FormSupportTest.java index f1535753d..c827a60cf 100644 --- a/support/jakarta-ee/src/test/java/org/apache/shiro/ee/filters/FormSupportTest.java +++ b/support/jakarta-ee/src/test/java/org/apache/shiro/ee/filters/FormSupportTest.java @@ -28,9 +28,9 @@ import java.util.Map; import javax.servlet.http.HttpServletRequest; import static org.apache.shiro.ee.util.JakartaTransformer.jakartify; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -46,38 +46,155 @@ import org.mockito.junit.jupiter.MockitoExtension; * Resubmit forms support */ @ExtendWith(MockitoExtension.class) -public class FormSupportTest { +class FormSupportTest { @Mock private HttpServletRequest request; @Test void nullReferer() { when(request.getHeader("referer")).thenReturn(null); - assertNull(getReferer(request)); + assertThat(getReferer(request)).isNull(); + } + + @Test + void blankReferer() { + when(request.getHeader("referer")).thenReturn(" "); + assertThat(getReferer(request)).isNull(); } @Test void plainStringReferer() { when(request.getHeader("referer")).thenReturn("hello"); - assertEquals("hello", getReferer(request)); + when(request.getContextPath()).thenReturn("/myapp"); + assertThat(getReferer(request)).isNull(); + } + + @Test + void malformedReferer() { + when(request.getHeader("referer")).thenReturn("http://exa mple.com"); + assertThat(getReferer(request)).isNull(); } @Test - void switchToHttps() { - when(request.getHeader("referer")).thenReturn("http://example.com"); - assertEquals("https://example.com", getReferer(request)); + void refererWithinContextPath() { + when(request.getHeader("referer")).thenReturn("https://example.com/myapp/login.xhtml"); + when(request.getContextPath()).thenReturn("/myapp"); + + assertThat(getReferer(request)).isEqualTo("/myapp/login.xhtml"); } @Test - void dontSwitchToHttpsWhenCustomPort() { - when(request.getHeader("referer")).thenReturn("http://example.com:8080/"); - assertEquals("http://example.com:8080/", getReferer(request)); + void refererWithinContextPathWithQuery() { + when(request.getHeader("referer")).thenReturn("https://example.com/myapp/login.xhtml?a=1&b=2"); + when(request.getContextPath()).thenReturn("/myapp"); + + assertThat(getReferer(request)).isEqualTo("/myapp/login.xhtml?a=1&b=2"); } @Test - void dontSwitchToHttpsWhenCustomPortNoTrailingSlash() { - when(request.getHeader("referer")).thenReturn("http://example.com:8080"); - assertEquals("http://example.com:8080", getReferer(request)); + void refererEqualToContextPathBecomesRoot() { + when(request.getHeader("referer")).thenReturn("https://example.com/myapp"); + when(request.getContextPath()).thenReturn("/myapp"); + + assertThat(getReferer(request)).isEqualTo("/myapp"); + } + + @Test + void refererOutsideContextPathIsRejected() { + when(request.getHeader("referer")).thenReturn("https://example.com/otherapp/login.xhtml"); + when(request.getContextPath()).thenReturn("/myapp"); + + assertThat(getReferer(request)).isNull(); + } + + @Test + void rootContextKeepsPath() { + when(request.getHeader("referer")).thenReturn("https://example.com/login.xhtml"); + when(request.getContextPath()).thenReturn(""); + + assertThat(getReferer(request)).isEqualTo("/login.xhtml"); + } + + @Test + void rootContextKeepsPathWithQuery() { + when(request.getHeader("referer")).thenReturn("https://example.com/login.xhtml?x=1"); + when(request.getContextPath()).thenReturn(""); + + assertThat(getReferer(request)).isEqualTo("/login.xhtml?x=1"); + } + + @Test + void normalizedPathWithinContextIsAccepted() { + when(request.getHeader("referer")).thenReturn("https://example.com/myapp//foo/./bar.xhtml"); + when(request.getContextPath()).thenReturn("/myapp"); + + assertThat(getReferer(request)).isEqualTo("/myapp/foo/bar.xhtml"); + } + + @Test + void normalizedPathEscapingContextIsRejected() { + when(request.getHeader("referer")).thenReturn("https://example.com/myapp/../otherapp/page.xhtml"); + when(request.getContextPath()).thenReturn("/myapp"); + + assertThat(getReferer(request)).isNull(); + } + + @Test + void opaqueUriRefererIsRejected() { + when(request.getHeader("referer")).thenReturn("mailto:[email protected]"); + when(request.getContextPath()).thenReturn("/myapp"); + + assertThat(getReferer(request)).isNull(); + } + + @Test + void javascriptUriRefererIsRejected() { + when(request.getHeader("referer")).thenReturn("javascript:alert(1)"); + when(request.getContextPath()).thenReturn("/myapp"); + + assertThat(getReferer(request)).isNull(); + } + + @Test + void contextPathPrefixMatchRequiresPathBoundary() { + when(request.getHeader("referer")).thenReturn("https://example.com/myapplication/page.xhtml"); + when(request.getContextPath()).thenReturn("/myapp"); + + assertThat(getReferer(request)).isNull(); + } + + @Test + void refererWithFragmentDropsFragmentAndKeepsQueryOnly() { + when(request.getHeader("referer")).thenReturn("https://example.com/myapp/page.xhtml?a=1#frag"); + when(request.getContextPath()).thenReturn("/myapp"); + + assertThat(getReferer(request)).isEqualTo("/myapp/page.xhtml?a=1"); + } + + @Test + void externalHostWithMatchingContextCurrentlyPasses() { + when(request.getHeader("referer")).thenReturn("https://attacker.example/myapp/login.xhtml"); + when(request.getContextPath()).thenReturn("/myapp"); + + assertThat(getReferer(request)).isEqualTo("/myapp/login.xhtml"); + } + + @Test + void encodedPathTraversalRefererIsRejected() { + when(request.getHeader("referer")) + .thenReturn("https://example.com/myapp/%2e%2e/otherapp/page.xhtml"); + when(request.getContextPath()).thenReturn("/myapp"); + + assertThat(getReferer(request)).isNull(); + } + + @Test + void encodedPathTraversalWithEncodedSlashesRefererIsRejected() { + when(request.getHeader("referer")) + .thenReturn("https://example.com/myapp/%2e%2e%2fotherapp%2fpage.xhtml"); + when(request.getContextPath()).thenReturn("/myapp"); + + assertThat(getReferer(request)).isNull(); } @Test
