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