You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jspwiki.apache.org by al...@apache.org on 2026/08/28 21:13:27 UTC
(jspwiki) 01/01: JSPWIKI-1300 see jira for details
This is an automated email from the ASF dual-hosted git repository.
spyhunter99 pushed a commit to branch bug/JSPWIKI-1300
in repository https://gitbox.apache.org/repos/asf/jspwiki.git
commit f3c7cebcd362ef19f86f6fdf211e7e0b13c59652
Author: Alex O'Ree <al...@apache.org>
AuthorDate: Fri Aug 28 17:13:02 2026 -0400
JSPWIKI-1300 see jira for details
---
.../apache/wiki/attachment/AttachmentServlet.java | 38 +++++++++---
.../wiki/attachment/AttachmentServletTest.java | 67 ++++++++++++++++++++++
2 files changed, 98 insertions(+), 7 deletions(-)
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/attachment/AttachmentServlet.java b/jspwiki-main/src/main/java/org/apache/wiki/attachment/AttachmentServlet.java
index bfae2a0ac..e3f10b2ae 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/attachment/AttachmentServlet.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/attachment/AttachmentServlet.java
@@ -379,16 +379,40 @@ public class AttachmentServlet extends HttpServlet {
* Fixes [JSPWIKI-46].
*/
private String validateNextPage( String nextPage, final String errorPage ) {
- if( nextPage.contains( "://" ) ) {
- // It's an absolute link, so unless it starts with our address, we'll log an error.
- if( !nextPage.startsWith( m_engine.getBaseURL() ) ) {
- LOG.warn("Detected phishing attempt by redirecting to an unsecure location: "+nextPage);
- nextPage = errorPage;
- }
- }
+ if( !isSameOrigin( nextPage, m_engine.getBaseURL() ) ) {
+ LOG.warn("Detected phishing attempt by redirecting to an unsecure location: "+nextPage);
+ nextPage = errorPage;
+ }
+
return nextPage;
}
+
+
+ /**
+ * Checks that a redirect target stays on this wiki. Rejects scheme-relative ({@code //host}) and
+ * backslash forms, which browsers resolve to a foreign origin even though they contain no {@code ://},
+ * and requires any URL with an explicit scheme to stay within the wiki's base URL on a path-segment
+ * boundary (so {@code http://host/wiki.evil.example} cannot spoof a base URL of {@code http://host/wiki}).
+ *
+ * @param nextPage redirect target to validate
+ * @param baseURL the wiki's configured base URL
+ * @return true if the target is safe to redirect to
+ */
+ static boolean isSameOrigin( final String nextPage, final String baseURL ) {
+ // Browsers ignore ASCII control characters and whitespace when parsing URLs, so strip them before validating.
+ final String url = nextPage.replaceAll( "[\\x00-\\x20]", "" );
+ if( url.startsWith( "//" ) || url.startsWith( "/\\" ) || url.startsWith( "\\" ) ) {
+ return false;
+ }
+ final int colon = url.indexOf( ':' );
+ if( colon != -1 && url.substring( 0, colon ).matches( "[a-zA-Z][a-zA-Z0-9+.-]*" ) ) {
+ // absolute URL with an explicit scheme: only allow it inside our own base URL
+ final String base = baseURL.endsWith( "/" ) ? baseURL : baseURL + "/";
+ return url.startsWith( base ) || url.equals( base.substring( 0, base.length() - 1 ) );
+ }
+ return true; // relative URL, resolves on this server
+ }
/**
* Uploads a specific mime multipart input set, intercepts exceptions.
diff --git a/jspwiki-main/src/test/java/org/apache/wiki/attachment/AttachmentServletTest.java b/jspwiki-main/src/test/java/org/apache/wiki/attachment/AttachmentServletTest.java
new file mode 100644
index 000000000..63c476134
--- /dev/null
+++ b/jspwiki-main/src/test/java/org/apache/wiki/attachment/AttachmentServletTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2026 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wiki.attachment;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+
+public class AttachmentServletTest {
+
+
+ private static final String BASE_URL = "http://localhost:8080/wiki";
+
+
+ /**
+ * Regression tests for the {@code nextpage} redirect validation (JSPWIKI-46 hardening): scheme-relative and
+ * scheme-colon URLs must not escape the same-origin check.
+ */
+ @Test
+ public void testRelativeUrlsAreAllowed() {
+ Assertions.assertTrue( AttachmentServlet.isSameOrigin( "/wiki/Wiki.jsp?page=Main", BASE_URL ) );
+ Assertions.assertTrue( AttachmentServlet.isSameOrigin( "Wiki.jsp?page=Main", BASE_URL ) );
+ Assertions.assertTrue( AttachmentServlet.isSameOrigin( "Wiki.jsp?page=Main:Sub", BASE_URL ) );
+ }
+
+ @Test
+ public void testSameOriginAbsoluteUrlIsAllowed() {
+ Assertions.assertTrue( AttachmentServlet.isSameOrigin( "http://localhost:8080/wiki/Upload.jsp?page=Main", BASE_URL ) );
+ Assertions.assertTrue( AttachmentServlet.isSameOrigin( "http://localhost:8080/wiki", BASE_URL ) );
+ }
+
+ @Test
+ public void testForeignAbsoluteUrlIsRejected() {
+ Assertions.assertFalse( AttachmentServlet.isSameOrigin( "http://evil.example/", BASE_URL ) );
+ Assertions.assertFalse( AttachmentServlet.isSameOrigin( "https://evil.example/", BASE_URL ) );
+ Assertions.assertFalse( AttachmentServlet.isSameOrigin( "https:evil.example", BASE_URL ) );
+ Assertions.assertFalse( AttachmentServlet.isSameOrigin( "javascript:alert(1)", BASE_URL ) );
+ }
+
+ @Test
+ public void testSchemeRelativeUrlIsRejected() {
+ Assertions.assertFalse( AttachmentServlet.isSameOrigin( "//evil.example", BASE_URL ) );
+ Assertions.assertFalse( AttachmentServlet.isSameOrigin( "/\\evil.example", BASE_URL ) );
+ Assertions.assertFalse( AttachmentServlet.isSameOrigin( "\\/evil.example", BASE_URL ) );
+ Assertions.assertFalse( AttachmentServlet.isSameOrigin( "/\t/evil.example", BASE_URL ) );
+ Assertions.assertFalse( AttachmentServlet.isSameOrigin( " //evil.example", BASE_URL ) );
+ }
+
+ @Test
+ public void testPrefixSpoofIsRejected() {
+ Assertions.assertFalse( AttachmentServlet.isSameOrigin( "http://localhost:8080/wiki.evil.example/", BASE_URL ) );
+ }
+
+}
\ No newline at end of file