From d35223c9abc4a70229086ac7bee66cf75fa77c41 Mon Sep 17 00:00:00 2001 From: Alex O'Ree Date: Sun, 23 Aug 2026 11:19:39 -0400 Subject: [PATCH] JSPWIKI-1287 see jira for details --- .../http/filter/CsrfProtectionFilterTest.java | 53 +++++++++++++++++++ jspwiki-war/src/main/webapp/Comment.jsp | 5 ++ jspwiki-war/src/main/webapp/Delete.jsp | 6 +++ jspwiki-war/src/main/webapp/Edit.jsp | 5 ++ jspwiki-war/src/main/webapp/Rename.jsp | 6 +++ jspwiki-war/src/main/webapp/Workflow.jsp | 7 +++ 6 files changed, 82 insertions(+) create mode 100644 jspwiki-http/src/test/java/org/apache/wiki/http/filter/CsrfProtectionFilterTest.java diff --git a/jspwiki-http/src/test/java/org/apache/wiki/http/filter/CsrfProtectionFilterTest.java b/jspwiki-http/src/test/java/org/apache/wiki/http/filter/CsrfProtectionFilterTest.java new file mode 100644 index 0000000000..30d3bbf576 --- /dev/null +++ b/jspwiki-http/src/test/java/org/apache/wiki/http/filter/CsrfProtectionFilterTest.java @@ -0,0 +1,53 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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.http.filter; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mockito; + +import jakarta.servlet.http.HttpServletRequest; + + +/** + * Tests for {@link CsrfProtectionFilter#isCsrfProtectedPost(HttpServletRequest)}, which state-changing JSPs + * (Delete.jsp, Rename.jsp, Workflow.jsp, Edit.jsp, Comment.jsp, DeleteGroup.jsp, etc.) rely on to reject + * cross-site requests that arrive with a method other than POST or without a valid anti-CSRF token. + */ +public class CsrfProtectionFilterTest { + + @ParameterizedTest + @ValueSource( strings = { "GET", "HEAD", "PUT", "DELETE", "OPTIONS", "TRACE", "PATCH" } ) + public void nonPostRequestIsNeverACsrfProtectedPost( final String method ) { + final HttpServletRequest request = Mockito.mock( HttpServletRequest.class ); + Mockito.doReturn( method ).when( request ).getMethod(); + Assertions.assertFalse( CsrfProtectionFilter.isCsrfProtectedPost( request ), + method + " request must not be accepted as a CSRF-protected POST" ); + } + + @ParameterizedTest + @ValueSource( strings = { "POST", "post", "Post" } ) + public void postMethodIsDetectedCaseInsensitively( final String method ) { + final HttpServletRequest request = Mockito.mock( HttpServletRequest.class ); + Mockito.doReturn( method ).when( request ).getMethod(); + Assertions.assertTrue( CsrfProtectionFilter.isPost( request ) ); + } + +} diff --git a/jspwiki-war/src/main/webapp/Comment.jsp b/jspwiki-war/src/main/webapp/Comment.jsp index 19803c6c58..df09734847 100644 --- a/jspwiki-war/src/main/webapp/Comment.jsp +++ b/jspwiki-war/src/main/webapp/Comment.jsp @@ -28,6 +28,7 @@ <%@ page import="org.apache.wiki.api.spi.Wiki" %> <%@ page import="org.apache.wiki.api.exceptions.RedirectException" %> <%@ page import="org.apache.wiki.auth.AuthorizationManager" %> +<%@ page import="org.apache.wiki.http.filter.CsrfProtectionFilter" %> <%@ page import="org.apache.wiki.auth.login.CookieAssertionLoginModule" %> <%@ page import="org.apache.wiki.filters.SpamFilter" %> <%@ page import="org.apache.wiki.htmltowiki.HtmlStringToWikiTranslator" %> @@ -144,6 +145,10 @@ log.debug("preview="+preview+", ok="+ok); if( ok != null ) { + if( !CsrfProtectionFilter.isCsrfProtectedPost( request ) ) { + response.sendRedirect( "/error/Forbidden.html" ); + return; + } log.info("Saving page "+pagereq+". User="+storedUser+", host="+HttpUtil.getRemoteAddress(request) ); // Modifications are written here before actual saving diff --git a/jspwiki-war/src/main/webapp/Delete.jsp b/jspwiki-war/src/main/webapp/Delete.jsp index 1081b5c20e..c3cb74b358 100644 --- a/jspwiki-war/src/main/webapp/Delete.jsp +++ b/jspwiki-war/src/main/webapp/Delete.jsp @@ -24,6 +24,7 @@ <%@ page import="org.apache.wiki.api.spi.Wiki" %> <%@ page import="org.apache.wiki.attachment.Attachment" %> <%@ page import="org.apache.wiki.auth.AuthorizationManager" %> +<%@ page import="org.apache.wiki.http.filter.CsrfProtectionFilter" %> <%@ page import="org.apache.wiki.pages.PageManager" %> <%@ page import="org.apache.wiki.preferences.Preferences" %> <%@ page import="org.apache.wiki.tags.BreadcrumbsTag" %> @@ -55,6 +56,11 @@ String delete = request.getParameter( "delete" ); String deleteall = request.getParameter( "delete-all" ); + if( ( delete != null || deleteall != null ) && !CsrfProtectionFilter.isCsrfProtectedPost( request ) ) { + response.sendRedirect( "/error/Forbidden.html" ); + return; + } + if( latestversion == null ) { latestversion = wikiContext.getPage(); diff --git a/jspwiki-war/src/main/webapp/Edit.jsp b/jspwiki-war/src/main/webapp/Edit.jsp index 762052f7b8..6d140154d1 100644 --- a/jspwiki-war/src/main/webapp/Edit.jsp +++ b/jspwiki-war/src/main/webapp/Edit.jsp @@ -25,6 +25,7 @@ <%@ page import="org.apache.wiki.api.exceptions.RedirectException" %> <%@ page import="org.apache.wiki.api.spi.Wiki" %> <%@ page import="org.apache.wiki.auth.AuthorizationManager" %> +<%@ page import="org.apache.wiki.http.filter.CsrfProtectionFilter" %> <%@ page import="org.apache.wiki.util.HttpUtil" %> <%@ page import="org.apache.wiki.filters.SpamFilter" %> <%@ page import="org.apache.wiki.htmltowiki.HtmlStringToWikiTranslator" %> @@ -118,6 +119,10 @@ log.debug("preview="+preview+", ok="+ok); if( ok != null || captcha != null ) { + if( !CsrfProtectionFilter.isCsrfProtectedPost( request ) ) { + response.sendRedirect( "/error/Forbidden.html" ); + return; + } log.info("Saving page "+pagereq+". User="+user+", host="+HttpUtil.getRemoteAddress(request) ); // diff --git a/jspwiki-war/src/main/webapp/Rename.jsp b/jspwiki-war/src/main/webapp/Rename.jsp index d456ac2632..16a5410636 100644 --- a/jspwiki-war/src/main/webapp/Rename.jsp +++ b/jspwiki-war/src/main/webapp/Rename.jsp @@ -25,6 +25,7 @@ <%@ page import="org.apache.wiki.api.spi.Wiki" %> <%@ page import="org.apache.wiki.api.exceptions.WikiException" %> <%@ page import="org.apache.wiki.auth.AuthorizationManager" %> +<%@ page import="org.apache.wiki.http.filter.CsrfProtectionFilter" %> <%@ page import="org.apache.wiki.content.PageRenamer" %> <%@ page import="org.apache.wiki.util.HttpUtil" %> <%@ page import="org.apache.wiki.preferences.Preferences" %> @@ -50,6 +51,11 @@ return; } + if( !CsrfProtectionFilter.isCsrfProtectedPost( request ) ) { + response.sendRedirect( "/error/Forbidden.html" ); + return; + } + String renameFrom = wikiContext.getName(); String renameTo = request.getParameter("renameto"); diff --git a/jspwiki-war/src/main/webapp/Workflow.jsp b/jspwiki-war/src/main/webapp/Workflow.jsp index 67d26388db..805b387389 100644 --- a/jspwiki-war/src/main/webapp/Workflow.jsp +++ b/jspwiki-war/src/main/webapp/Workflow.jsp @@ -27,6 +27,7 @@ <%@ page import="org.apache.wiki.api.core.Session" %> <%@ page import="org.apache.wiki.api.spi.Wiki" %> <%@ page import="org.apache.wiki.auth.AuthorizationManager" %> +<%@ page import="org.apache.wiki.http.filter.CsrfProtectionFilter" %> <%@ page import="org.apache.wiki.preferences.Preferences" %> <%@ page import="org.apache.wiki.ui.TemplateManager" %> <%@ page import="org.apache.wiki.workflow.Decision" %> @@ -53,6 +54,12 @@ // Get the current decisions DecisionQueue dq = wiki.getManager( WorkflowManager.class ).getDecisionQueue(); + String action = request.getParameter( "action" ); + if( ( "decide".equals( action ) || "abort".equals( action ) ) && !CsrfProtectionFilter.isCsrfProtectedPost( request ) ) { + response.sendRedirect( "/error/Forbidden.html" ); + return; + } + if( "decide".equals(request.getParameter("action")) ) { try