diff --git a/ChangeLog.md b/ChangeLog.md index 4c8c882d0d..79e76b393f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -17,6 +17,12 @@ specific language governing permissions and limitations under the License. --> +**2023-11-26 Arturo Bernal (juanpablo AT apache DOT org)** + +* _2.12.2-git-10_ + +* [JSPWIKI-1138](https://issues.apache.org/jira/browse/JSPWIKI-1138) Fix XSS vulnerability in property handling + **2023-11-25 Juan Pablo Santos (juanpablo AT apache DOT org)** * _2.12.2-git-09_ diff --git a/jspwiki-api/src/main/java/org/apache/wiki/api/Release.java b/jspwiki-api/src/main/java/org/apache/wiki/api/Release.java index 97c8502516..ff567b0493 100644 --- a/jspwiki-api/src/main/java/org/apache/wiki/api/Release.java +++ b/jspwiki-api/src/main/java/org/apache/wiki/api/Release.java @@ -69,7 +69,7 @@ public final class Release { *

* If the build identifier is empty, it is not added. */ - public static final String BUILD = "09"; + public static final String BUILD = "10"; /** * This is the generic version string you should use when printing out the version. It is of diff --git a/jspwiki-main/src/main/java/org/apache/wiki/ui/Installer.java b/jspwiki-main/src/main/java/org/apache/wiki/ui/Installer.java index 570d9722e0..28059a9b61 100644 --- a/jspwiki-main/src/main/java/org/apache/wiki/ui/Installer.java +++ b/jspwiki-main/src/main/java/org/apache/wiki/ui/Installer.java @@ -18,6 +18,7 @@ Licensed to the Apache Software Foundation (ASF) under one */ package org.apache.wiki.ui; +import org.apache.commons.io.FilenameUtils; import org.apache.wiki.api.core.Engine; import org.apache.wiki.api.core.Session; import org.apache.wiki.api.providers.AttachmentProvider; @@ -181,17 +182,18 @@ public void parseProperties () { // Get application name String nullValue = m_props.getProperty( APP_NAME, rb.getString( "install.installer.default.appname" ) ); - parseProperty( APP_NAME, nullValue ); + parseProperty( APP_NAME, nullValue ); + sanitizeInput( APP_NAME ); // Get/sanitize page directory nullValue = m_props.getProperty( PAGE_DIR, rb.getString( "install.installer.default.pagedir" ) ); parseProperty( PAGE_DIR, nullValue ); - sanitizePath( PAGE_DIR ); + sanitizeAndNormalizePath( PAGE_DIR ); // Get/sanitize work directory nullValue = m_props.getProperty( WORK_DIR, TMP_DIR ); parseProperty( WORK_DIR, nullValue ); - sanitizePath( WORK_DIR ); + sanitizeAndNormalizePath( WORK_DIR ); // Set a few more default properties, for easy setup m_props.setProperty( STORAGE_DIR, m_props.getProperty( PAGE_DIR ) ); @@ -239,17 +241,34 @@ private void parseProperty( final String param, final String defaultValue ) { } m_props.put( param, value ); } - + /** - * Simply sanitizes any path which contains backslashes (sometimes Windows users may have them) by expanding them to double-backslashes + * Sanitizes and normalizes the file path associated with the specified property key. + *

+ * This method performs a two-step process on the property value: + *

    + *
  1. Sanitization: It first sanitizes the input to mitigate the risk of Cross-Site Scripting (XSS) attacks. + * This is achieved by replacing specific characters known to be used in XSS attacks with their corresponding + * HTML entity codes. The characters '<', '>', '"', ''', and '/' are replaced with "<", ">", """, + * "'", and "/", respectively.
  2. + *
  3. Normalization: After sanitization, the method normalizes the file path using + * {@link org.apache.commons.io.FilenameUtils#normalizeNoEndSeparator(String)}. This normalization process + * ensures that the file path is in a consistent format, which is particularly important for file system + * operations, especially in environments like Windows where backslashes are used.
  4. + *
+ * If the property key is null or the property value is not set, the method performs no action. + *

* - * @param key the key of the property to sanitize + * @param key The key of the property whose value represents a file path. This key is used to retrieve, + * sanitize, and normalize the property value. If the key is null, no action is taken. */ - private void sanitizePath( final String key ) { - String s = m_props.getProperty( key ); - s = TextUtil.replaceString(s, "\\", "\\\\" ); - s = s.trim(); - m_props.put( key, s ); + private void sanitizeAndNormalizePath( final String key ) { + sanitizeInput( key ); // Sanitize the input for XSS protection + final String sanitizedValue = m_props.getProperty( key ); + if( sanitizedValue != null ) { + final String normalizedPath = FilenameUtils.normalizeNoEndSeparator( sanitizedValue ); + m_props.put( key, normalizedPath ); + } } private void validateNotNull( final String key, final String message ) { @@ -258,5 +277,22 @@ private void validateNotNull( final String key, final String message ) { m_session.addMessage( INSTALL_ERROR, message ); } } + + /** + * Sanitizes the property value associated with the specified key to prevent Cross-Site Scripting (XSS) attacks. + * This method retrieves the property value for the given key from the properties map, then replaces certain + * characters with their corresponding HTML entity codes to mitigate the risk of XSS vulnerabilities. + * The characters '<', '>', '"', ''', and '/' are replaced with "<", ">", """, "'", and "/", respectively. + * If the key is null, the method returns without making any changes. + * + * @param key The key of the property to be sanitized. If the key is null, no action is taken. + */ + private void sanitizeInput( final String key ) { + m_props.put( key, m_props.getProperty( key ).replaceAll( "<", "<" ) + .replaceAll( ">", ">" ) + .replaceAll( "\"", """ ) + .replaceAll( "'", "'" ) + .replaceAll( "/", "/" ) ); + } } diff --git a/jspwiki-main/src/test/java/org/apache/wiki/ui/InstallerTest.java b/jspwiki-main/src/test/java/org/apache/wiki/ui/InstallerTest.java new file mode 100644 index 0000000000..8e99d1f4dc --- /dev/null +++ b/jspwiki-main/src/test/java/org/apache/wiki/ui/InstallerTest.java @@ -0,0 +1,112 @@ +/* + 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. + */ +/* + * (C) Janne Jalkanen 2005 + * + */ + +package org.apache.wiki.ui; + +import net.sourceforge.stripes.mock.MockHttpServletRequest; +import net.sourceforge.stripes.mock.MockServletConfig; +import org.apache.wiki.TestEngine; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import javax.servlet.ServletException; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +public class InstallerTest { + + private Installer installer; + + + @BeforeEach + public void setUp() throws ServletException { + + + final IntallerMockHttpServletRequest req = new IntallerMockHttpServletRequest( "/JSPWiki", "/wiki/Wiki.jsp" ); + + + req.setParameter( "jspwiki.applicationName", "" ); + req.setParameter( "jspwiki.workDir", "C:\\Users\\Example\\Path" ); + req.setParameter( "jspwiki.fileSystemProvider.pageDir", "" ); + + + final MockServletConfig config = new MockServletConfig(); + config.setServletContext( TestEngine.createServletContext( "/JSPWiki" ) ); + + + installer = new Installer( req, config ); + } + + @Test + public void testSanitizeInput() { + // Assuming there's a method in Installer that processes the request and sets properties + installer.parseProperties(); + + // Verify the property is sanitized + String sanitizedValue = installer.getProperty( "jspwiki.applicationName" ); + assertEquals( "<script>alert('xss');</script>", sanitizedValue ); + } + + @Test + public void testSanitizePath() { + // Assuming there's a method in Installer that processes the request and sets properties + installer.parseProperties(); + + // Verify the path is normalized + String normalizedPath = installer.getProperty( "jspwiki.workDir" ); + assertEquals( "C:/Users/Example/Path", normalizedPath ); // Adjust expected value based on normalization behavior + } + + @Test + public void testSanitizePageDir() { + // Assuming there's a method in Installer that processes the request and sets properties + installer.parseProperties(); + + // Verify the path is normalized + String sanitizedValue = installer.getProperty( "jspwiki.fileSystemProvider.pageDir" ); + assertEquals( "<script>alert2('xss');</script>", sanitizedValue ); + } + + + static class IntallerMockHttpServletRequest extends MockHttpServletRequest { + private final Map< String, String > parameters = new HashMap<>(); + + public IntallerMockHttpServletRequest( String contextPath, String servletPath ) { + super( contextPath, servletPath ); + } + + @Override + public String getParameter( String name ) { + return parameters.get( name ); + } + + public void setParameter( String name, String value ) { + parameters.put( name, value ); + } + } + +} +