You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shiro.apache.org by lp...@apache.org on 2026/05/24 00:57:18 UTC
(shiro) 01/03: enh(jakarta-ee): strip out the host part of the referer header
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 5ed8730eaf86736922aa74eb6703969cc80da20e
Author: lprimak <le...@flowlogix.com>
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 | 137 +++++++++++++++++++--
2 files changed, 150 insertions(+), 15 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 695165a72..02c9164de 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
@@ -258,13 +258,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 8272823d8..99dcfdb64 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
@@ -53,28 +53,145 @@ class FormSupportTest {
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");
- assertThat(getReferer(request)).isEqualTo("hello");
+ 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 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 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 switchToHttps() {
- when(request.getHeader("referer")).thenReturn("http://example.com");
- assertThat(getReferer(request)).isEqualTo("https://example.com");
+ void refererEqualToContextPathBecomesRoot() {
+ when(request.getHeader("referer")).thenReturn("https://example.com/myapp");
+ when(request.getContextPath()).thenReturn("/myapp");
+
+ assertThat(getReferer(request)).isEqualTo("/myapp");
}
@Test
- void dontSwitchToHttpsWhenCustomPort() {
- when(request.getHeader("referer")).thenReturn("http://example.com:8080/");
- assertThat(getReferer(request)).isEqualTo("http://example.com:8080/");
+ void refererOutsideContextPathIsRejected() {
+ when(request.getHeader("referer")).thenReturn("https://example.com/otherapp/login.xhtml");
+ when(request.getContextPath()).thenReturn("/myapp");
+
+ assertThat(getReferer(request)).isNull();
}
@Test
- void dontSwitchToHttpsWhenCustomPortNoTrailingSlash() {
- when(request.getHeader("referer")).thenReturn("http://example.com:8080");
- assertThat(getReferer(request)).isEqualTo("http://example.com:8080");
+ 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:test@example.com");
+ 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