Skip to content

Commit bd278bc

Browse files
committed
enh(jakarta-ee): strip out the host part of the referer header
1 parent c6af28c commit bd278bc

2 files changed

Lines changed: 153 additions & 18 deletions

File tree

support/jakarta-ee/src/main/java/org/apache/shiro/ee/filters/FormResubmitSupport.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -259,13 +259,31 @@ static void saveRequestReferer(boolean rv, HttpServletRequest request, HttpServl
259259

260260
static String getReferer(HttpServletRequest request) {
261261
String referer = request.getHeader("referer");
262-
if (referer != null) {
263-
// do not switch to https if custom port is specified
264-
if (!referer.matches("^http:\\/\\/[A-z|.|[0-9]]+:[0-9]+(\\/.*|$)")) {
265-
referer = referer.replaceFirst("^http:", "https:");
262+
if (referer == null || referer.isBlank()) {
263+
return null;
264+
}
265+
266+
try {
267+
URI uri = URI.create(referer);
268+
269+
String contextPath = WebUtils.getContextPath(request);
270+
String path = WebUtils.normalize(uri.getPath());
271+
272+
if (path == null) {
273+
return null;
266274
}
275+
276+
if (!contextPath.isEmpty()
277+
&& !path.equals(contextPath)
278+
&& !path.startsWith(contextPath + "/")) {
279+
return null;
280+
}
281+
282+
String query = uri.getRawQuery();
283+
return query == null ? path : path + "?" + query;
284+
} catch (IllegalArgumentException e) {
285+
return null;
267286
}
268-
return referer;
269287
}
270288

271289
/**

support/jakarta-ee/src/test/java/org/apache/shiro/ee/filters/FormSupportTest.java

Lines changed: 130 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@
2828
import javax.servlet.http.HttpServletRequest;
2929

3030
import static org.apache.shiro.ee.util.JakartaTransformer.jakartify;
31+
import static org.assertj.core.api.Assertions.assertThat;
3132
import static org.junit.jupiter.api.Assertions.assertEquals;
3233
import static org.junit.jupiter.api.Assertions.assertFalse;
33-
import static org.junit.jupiter.api.Assertions.assertNull;
3434
import static org.junit.jupiter.api.Assertions.assertThrows;
3535
import static org.junit.jupiter.api.Assertions.assertTrue;
3636

@@ -46,38 +46,155 @@
4646
* Resubmit forms support
4747
*/
4848
@ExtendWith(MockitoExtension.class)
49-
public class FormSupportTest {
49+
class FormSupportTest {
5050
@Mock
5151
private HttpServletRequest request;
5252

5353
@Test
5454
void nullReferer() {
5555
when(request.getHeader("referer")).thenReturn(null);
56-
assertNull(getReferer(request));
56+
assertThat(getReferer(request)).isNull();
57+
}
58+
59+
@Test
60+
void blankReferer() {
61+
when(request.getHeader("referer")).thenReturn(" ");
62+
assertThat(getReferer(request)).isNull();
5763
}
5864

5965
@Test
6066
void plainStringReferer() {
6167
when(request.getHeader("referer")).thenReturn("hello");
62-
assertEquals("hello", getReferer(request));
68+
when(request.getContextPath()).thenReturn("/myapp");
69+
assertThat(getReferer(request)).isNull();
70+
}
71+
72+
@Test
73+
void malformedReferer() {
74+
when(request.getHeader("referer")).thenReturn("http://exa mple.com");
75+
assertThat(getReferer(request)).isNull();
6376
}
6477

6578
@Test
66-
void switchToHttps() {
67-
when(request.getHeader("referer")).thenReturn("http://example.com");
68-
assertEquals("https://example.com", getReferer(request));
79+
void refererWithinContextPath() {
80+
when(request.getHeader("referer")).thenReturn("https://example.com/myapp/login.xhtml");
81+
when(request.getContextPath()).thenReturn("/myapp");
82+
83+
assertThat(getReferer(request)).isEqualTo("/myapp/login.xhtml");
6984
}
7085

7186
@Test
72-
void dontSwitchToHttpsWhenCustomPort() {
73-
when(request.getHeader("referer")).thenReturn("http://example.com:8080/");
74-
assertEquals("http://example.com:8080/", getReferer(request));
87+
void refererWithinContextPathWithQuery() {
88+
when(request.getHeader("referer")).thenReturn("https://example.com/myapp/login.xhtml?a=1&b=2");
89+
when(request.getContextPath()).thenReturn("/myapp");
90+
91+
assertThat(getReferer(request)).isEqualTo("/myapp/login.xhtml?a=1&b=2");
7592
}
7693

7794
@Test
78-
void dontSwitchToHttpsWhenCustomPortNoTrailingSlash() {
79-
when(request.getHeader("referer")).thenReturn("http://example.com:8080");
80-
assertEquals("http://example.com:8080", getReferer(request));
95+
void refererEqualToContextPathBecomesRoot() {
96+
when(request.getHeader("referer")).thenReturn("https://example.com/myapp");
97+
when(request.getContextPath()).thenReturn("/myapp");
98+
99+
assertThat(getReferer(request)).isEqualTo("/myapp");
100+
}
101+
102+
@Test
103+
void refererOutsideContextPathIsRejected() {
104+
when(request.getHeader("referer")).thenReturn("https://example.com/otherapp/login.xhtml");
105+
when(request.getContextPath()).thenReturn("/myapp");
106+
107+
assertThat(getReferer(request)).isNull();
108+
}
109+
110+
@Test
111+
void rootContextKeepsPath() {
112+
when(request.getHeader("referer")).thenReturn("https://example.com/login.xhtml");
113+
when(request.getContextPath()).thenReturn("");
114+
115+
assertThat(getReferer(request)).isEqualTo("/login.xhtml");
116+
}
117+
118+
@Test
119+
void rootContextKeepsPathWithQuery() {
120+
when(request.getHeader("referer")).thenReturn("https://example.com/login.xhtml?x=1");
121+
when(request.getContextPath()).thenReturn("");
122+
123+
assertThat(getReferer(request)).isEqualTo("/login.xhtml?x=1");
124+
}
125+
126+
@Test
127+
void normalizedPathWithinContextIsAccepted() {
128+
when(request.getHeader("referer")).thenReturn("https://example.com/myapp//foo/./bar.xhtml");
129+
when(request.getContextPath()).thenReturn("/myapp");
130+
131+
assertThat(getReferer(request)).isEqualTo("/myapp/foo/bar.xhtml");
132+
}
133+
134+
@Test
135+
void normalizedPathEscapingContextIsRejected() {
136+
when(request.getHeader("referer")).thenReturn("https://example.com/myapp/../otherapp/page.xhtml");
137+
when(request.getContextPath()).thenReturn("/myapp");
138+
139+
assertThat(getReferer(request)).isNull();
140+
}
141+
142+
@Test
143+
void opaqueUriRefererIsRejected() {
144+
when(request.getHeader("referer")).thenReturn("mailto:test@example.com");
145+
when(request.getContextPath()).thenReturn("/myapp");
146+
147+
assertThat(getReferer(request)).isNull();
148+
}
149+
150+
@Test
151+
void javascriptUriRefererIsRejected() {
152+
when(request.getHeader("referer")).thenReturn("javascript:alert(1)");
153+
when(request.getContextPath()).thenReturn("/myapp");
154+
155+
assertThat(getReferer(request)).isNull();
156+
}
157+
158+
@Test
159+
void contextPathPrefixMatchRequiresPathBoundary() {
160+
when(request.getHeader("referer")).thenReturn("https://example.com/myapplication/page.xhtml");
161+
when(request.getContextPath()).thenReturn("/myapp");
162+
163+
assertThat(getReferer(request)).isNull();
164+
}
165+
166+
@Test
167+
void refererWithFragmentDropsFragmentAndKeepsQueryOnly() {
168+
when(request.getHeader("referer")).thenReturn("https://example.com/myapp/page.xhtml?a=1#frag");
169+
when(request.getContextPath()).thenReturn("/myapp");
170+
171+
assertThat(getReferer(request)).isEqualTo("/myapp/page.xhtml?a=1");
172+
}
173+
174+
@Test
175+
void externalHostWithMatchingContextCurrentlyPasses() {
176+
when(request.getHeader("referer")).thenReturn("https://attacker.example/myapp/login.xhtml");
177+
when(request.getContextPath()).thenReturn("/myapp");
178+
179+
assertThat(getReferer(request)).isEqualTo("/myapp/login.xhtml");
180+
}
181+
182+
@Test
183+
void encodedPathTraversalRefererIsRejected() {
184+
when(request.getHeader("referer"))
185+
.thenReturn("https://example.com/myapp/%2e%2e/otherapp/page.xhtml");
186+
when(request.getContextPath()).thenReturn("/myapp");
187+
188+
assertThat(getReferer(request)).isNull();
189+
}
190+
191+
@Test
192+
void encodedPathTraversalWithEncodedSlashesRefererIsRejected() {
193+
when(request.getHeader("referer"))
194+
.thenReturn("https://example.com/myapp/%2e%2e%2fotherapp%2fpage.xhtml");
195+
when(request.getContextPath()).thenReturn("/myapp");
196+
197+
assertThat(getReferer(request)).isNull();
81198
}
82199

83200
@Test

0 commit comments

Comments
 (0)