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/23 19:21:12 UTC

(jspwiki) 01/01: JSPWIKI-1298 see jira for details

This is an automated email from the ASF dual-hosted git repository.

spyhunter99 pushed a commit to branch bug/JSPWIKI-1298
in repository https://gitbox.apache.org/repos/asf/jspwiki.git

commit adc4af6d09587429404252ceb2315f55670d4846
Author: Alex O'Ree <al...@apache.org>
AuthorDate: Sun Aug 23 15:20:56 2026 -0400

    JSPWIKI-1298 see jira for details
---
 .../wiki/http/filter/CsrfProtectionFilter.java      | 21 ++++++++++++++++++++-
 .../wiki/http/filter/ReferrerPolicyFilter.java      |  4 +++-
 .../apache/wiki/ajax/WikiAjaxDispatcherServlet.java |  7 ++++++-
 .../apache/wiki/attachment/AttachmentServlet.java   | 15 +++++++++++++++
 .../src/main/webapp/templates/210/AttachmentTab.jsp |  5 +++--
 .../main/webapp/templates/default/AttachmentTab.jsp |  5 +++--
 6 files changed, 50 insertions(+), 7 deletions(-)

diff --git a/jspwiki-http/src/main/java/org/apache/wiki/http/filter/CsrfProtectionFilter.java b/jspwiki-http/src/main/java/org/apache/wiki/http/filter/CsrfProtectionFilter.java
index cc15c05e2..cd43cf5a5 100644
--- a/jspwiki-http/src/main/java/org/apache/wiki/http/filter/CsrfProtectionFilter.java
+++ b/jspwiki-http/src/main/java/org/apache/wiki/http/filter/CsrfProtectionFilter.java
@@ -33,6 +33,7 @@ import jakarta.servlet.ServletResponse;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.util.Locale;
 
 
 /**
@@ -54,7 +55,7 @@ public class CsrfProtectionFilter implements Filter {
     /** {@inheritDoc} */
     @Override
     public void doFilter( final ServletRequest request, final ServletResponse response, final FilterChain chain ) throws IOException, ServletException {
-        if( isPost( ( HttpServletRequest ) request ) ) {
+        if( isPost( ( HttpServletRequest ) request ) && !isMultipartAttachmentUpload( ( HttpServletRequest ) request ) ) {
             final Engine engine = Wiki.engine().find( request.getServletContext(), null );
             final Session session = Wiki.session().find( engine, ( HttpServletRequest ) request );
             if( !requestContainsValidCsrfToken( request, session ) ) {
@@ -84,6 +85,24 @@ public class CsrfProtectionFilter implements Filter {
         return "POST".equalsIgnoreCase( request.getMethod() );
     }
 
+    /**
+     * Multipart uploads carry their anti-CSRF token in the request body, which {@code getParameter()} cannot read
+     * without consuming the stream. Requiring the token as a request parameter would force it into the URL query
+     * string, where it leaks into access logs, proxies and browser history. For the attachment servlet - the only
+     * multipart consumer, which parses the body itself and performs the same token check before acting on the
+     * request - the check is therefore delegated instead of applied here. Multipart POSTs to any other path keep
+     * being validated by this filter, so the body trick cannot be used to smuggle query-string parameters past it.
+     *
+     * @param request the inbound request
+     * @return true if this is a multipart POST for the attachment servlet
+     */
+    private static boolean isMultipartAttachmentUpload( final HttpServletRequest request ) {
+        final String contentType = request.getContentType();
+        return "/attach".equals( request.getServletPath() )
+                && contentType != null
+                && contentType.toLowerCase( Locale.ENGLISH ).startsWith( "multipart/" );
+    }
+
     /** {@inheritDoc} */
     @Override
     public void destroy() {
diff --git a/jspwiki-http/src/main/java/org/apache/wiki/http/filter/ReferrerPolicyFilter.java b/jspwiki-http/src/main/java/org/apache/wiki/http/filter/ReferrerPolicyFilter.java
index 8067664a6..717661481 100644
--- a/jspwiki-http/src/main/java/org/apache/wiki/http/filter/ReferrerPolicyFilter.java
+++ b/jspwiki-http/src/main/java/org/apache/wiki/http/filter/ReferrerPolicyFilter.java
@@ -32,7 +32,9 @@ import java.io.IOException;
  */
 public class ReferrerPolicyFilter implements Filter {
 
-    private String mode = "no-referrer-when-downgrade";
+    // Default to same-origin: wiki URLs can carry sensitive query parameters, so the URL must never reach a
+    // third-party destination via the Referer header. Deployments may still override via ReferrerPolicyPValue.
+    private String mode = "same-origin";
 
     /** {@inheritDoc} */
     @Override
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/ajax/WikiAjaxDispatcherServlet.java b/jspwiki-main/src/main/java/org/apache/wiki/ajax/WikiAjaxDispatcherServlet.java
index 5883971f4..05ad4fa64 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/ajax/WikiAjaxDispatcherServlet.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/ajax/WikiAjaxDispatcherServlet.java
@@ -140,7 +140,12 @@ public class WikiAjaxDispatcherServlet extends HttpServlet {
                     res.setCharacterEncoding( m_engine.getContentEncoding().displayName() );
                     final String actionName = AjaxUtil.getNextPathPart( req.getRequestURI(), servlet.getServletMapping() );
                     if (!(servlet instanceof DefaultSearchManager.PluginSearch)) {
-                        final String xsrfToken = req.getParameter("X-XSRF-TOKEN");
+                        // Prefer the request header: a token sent as a GET parameter ends up in access logs and
+                        // browser history. The parameter is still accepted for compatibility with older clients.
+                        String xsrfToken = req.getHeader("X-XSRF-TOKEN");
+                        if (xsrfToken == null) {
+                            xsrfToken = req.getParameter("X-XSRF-TOKEN");
+                        }
                         if (!wikiSession.antiCsrfToken().equals(xsrfToken)) {
                             res.sendError(400, "X-XSRF-TOKEN missing or invalid.");
                             WikiEventManager.fireEvent(this,
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..fa41de7e2 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
@@ -51,6 +51,7 @@ import org.apache.wiki.api.providers.WikiProvider;
 import org.apache.wiki.api.spi.Wiki;
 import org.apache.wiki.auth.AuthorizationManager;
 import org.apache.wiki.auth.permissions.PermissionFactory;
+import org.apache.wiki.http.filter.CsrfProtectionFilter;
 import org.apache.wiki.i18n.InternationalizationManager;
 import org.apache.wiki.preferences.Preferences;
 import org.apache.wiki.ui.progress.ProgressItem;
@@ -406,6 +407,7 @@ public class AttachmentServlet extends HttpServlet {
         final String errorPage = m_engine.getURL( ContextEnum.WIKI_ERROR.getRequestContext(), "", null ); // If something bad happened, Upload should be able to take care of most stuff
         String nextPage = errorPage;
         final String progressId = req.getParameter( "progressid" );
+        String csrfToken = req.getParameter( CsrfProtectionFilter.ANTICSRF_PARAM );
 
         // Check that we have a file upload request
         if( !JakartaServletFileUpload.isMultipartContent(req) ) {
@@ -463,12 +465,25 @@ public class AttachmentServlet extends HttpServlet {
                     case "nextpage":
                         nextPage = validateNextPage( item.getString( StandardCharsets.UTF_8 ), errorPage );
                         break;
+                    case CsrfProtectionFilter.ANTICSRF_PARAM:
+                        csrfToken = item.getString( StandardCharsets.UTF_8 );
+                        break;
                     }
                 } else {
                     fileItems.add( item );
                 }
             }
 
+            // The CsrfProtectionFilter delegates multipart requests to this servlet, since it cannot read the
+            // token from a multipart body without consuming it. Enforce the token here - taken from the multipart
+            // form field written by <wiki:CsrfProtection/> (or, for older clients, the query string) - before
+            // acting on anything else in the request, including the "Broken file upload" redirect below.
+            final Session session = Wiki.session().find( m_engine, req );
+            if( csrfToken == null || !csrfToken.equals( session.antiCsrfToken() ) ) {
+                LOG.error( "Incorrect {} received for {}", CsrfProtectionFilter.ANTICSRF_PARAM, req.getPathInfo() );
+                throw new RedirectException( "Missing or invalid CSRF token", errorPage );
+            }
+
             if(fileItems.isEmpty()) {
                 throw new RedirectException( "Broken file upload", nextPage );
 
diff --git a/jspwiki-war/src/main/webapp/templates/210/AttachmentTab.jsp b/jspwiki-war/src/main/webapp/templates/210/AttachmentTab.jsp
index a549ebeb1..e9d85846d 100644
--- a/jspwiki-war/src/main/webapp/templates/210/AttachmentTab.jsp
+++ b/jspwiki-war/src/main/webapp/templates/210/AttachmentTab.jsp
@@ -31,12 +31,13 @@
   Context c = Context.findContext(pageContext);
 %>
 <c:set var="progressId" value="<%= c.getEngine().getManager( ProgressManager.class ).getNewProgressIdentifier() %>" />
-<c:set var="csrfProtection" value="<%= c.getWikiSession().antiCsrfToken() %>" />
 
 <div id="addattachment">
 <h3><fmt:message key="attach.add"/></h3>
 <wiki:Permission permission="upload">
-  <form action="<wiki:Link jsp='attach' format='url'><wiki:Param name='progressid' value='${progressId}'/><wiki:Param name='X-XSRF-TOKEN' value='${csrfProtection}'/></wiki:Link>"
+  <%-- The anti-CSRF token travels as a hidden multipart form field (see <wiki:CsrfProtection/> below), NOT as a
+       query-string parameter: query strings end up in access logs, proxies and browser history. --%>
+  <form action="<wiki:Link jsp='attach' format='url'><wiki:Param name='progressid' value='${progressId}'/></wiki:Link>"
          class="wikiform"
             id="uploadform"
         method="post"
diff --git a/jspwiki-war/src/main/webapp/templates/default/AttachmentTab.jsp b/jspwiki-war/src/main/webapp/templates/default/AttachmentTab.jsp
index 5c734a2b9..fb34a1f18 100644
--- a/jspwiki-war/src/main/webapp/templates/default/AttachmentTab.jsp
+++ b/jspwiki-war/src/main/webapp/templates/default/AttachmentTab.jsp
@@ -33,11 +33,12 @@
   Context c = Context.findContext(pageContext);
 %>
 <c:set var="progressId" value="<%= c.getEngine().getManager( ProgressManager.class ).getNewProgressIdentifier() %>" />
-<c:set var="csrfProtection" value="<%= c.getWikiSession().antiCsrfToken() %>" />
 <div class="page-content">
 <wiki:Permission permission="upload">
 
-  <form action="<wiki:Link jsp='attach' format='url'><wiki:Param name='progressid' value='${progressId}'/><wiki:Param name='X-XSRF-TOKEN' value='${csrfProtection}'/></wiki:Link>"
+  <%-- The anti-CSRF token travels as a hidden multipart form field (see <wiki:CsrfProtection/> below), NOT as a
+       query-string parameter: query strings end up in access logs, proxies and browser history. --%>
+  <form action="<wiki:Link jsp='attach' format='url'><wiki:Param name='progressid' value='${progressId}'/></wiki:Link>"
          class="accordion<wiki:HasAttachments></wiki:HasAttachments>"
             id="uploadform"
         method="post"