CVE-2022-29253: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: XWiki Platform is a generic wiki platform offering runtime services for applications built on top of it. Starting with version 8.3-rc-1 and prior to versions 12.10.3 and 14.0, one can ask for any file located in the classloader using the template API and a path with ".." in it. The issue is patched in versions 14.0 and 13.10.3. There is no easy workaround for this issue.
CWE description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
CVSS v3 base score: 2.7
Published: 2022-05-25T21:15Z
Repository: https://github.com/xwiki/xwiki-platform
Language: Java
Files changed: xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/skin/AbstractResourceSkin.java, xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/template/InternalTemplateManager.java
Commit date: 2022-01-28 11:39:55 +0100
Commit message: XWIKI-19349: Bad handling of classloader templates path resolution

Diff: diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/skin/AbstractResourceSkin.java b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/skin/AbstractResourceSkin.java
index 046339d14003..8ed33a5c355c 100644
--- a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/skin/AbstractResourceSkin.java
+++ b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/skin/AbstractResourceSkin.java
@@ -140,11 +140,11 @@ protected Configuration getProperties()
     private String getSkinResourcePath(String resource)
     {
         String skinFolder = getSkinFolder();
         String resourcePath = skinFolder + resource;
 
-        // Prevent inclusion of templates from other directories
+        // Prevent access to resources from other directories
         Path normalizedResource = Paths.get(resourcePath).normalize();
         // Protect against directory attacks.
         if (!normalizedResource.startsWith(skinFolder)) {
             LOGGER.warn("Direct access to skin file [{}] refused. Possible break-in attempt!", normalizedResource);
             return null;
diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/template/InternalTemplateManager.java b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/template/InternalTemplateManager.java
index 770cbf17d7c8..b81c43728bad 100644
--- a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/template/InternalTemplateManager.java
+++ b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/template/InternalTemplateManager.java
@@ -22,10 +22,12 @@
 import java.io.StringWriter;
 import java.io.Writer;
 import java.lang.reflect.Type;
 import java.net.URL;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.AbstractSet;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -894,18 +896,27 @@ private EnvironmentTemplate getFileSystemTemplate(String templateName)
 
         return path != null ? new EnvironmentTemplate(new TemplateSkinResource(path, templateName, this.environment))
             : null;
     }
 
-    private Template getClassloaderTemplate(String suffixPath, String templateName)
+    private Template getClassloaderTemplate(String prefixPath, String templateName)
     {
-        return getClassloaderTemplate(Thread.currentThread().getContextClassLoader(), suffixPath, templateName);
+        return getClassloaderTemplate(Thread.currentThread().getContextClassLoader(), prefixPath, templateName);
     }
 
-    private Template getClassloaderTemplate(ClassLoader classloader, String suffixPath, String templateName)
+    private Template getClassloaderTemplate(ClassLoader classloader, String prefixPath, String templateName)
     {
-        String templatePath = suffixPath + templateName;
+        String templatePath = prefixPath + templateName;
+
+        // Prevent access to resources from other directories
+        Path normalizedResource = Paths.get(templatePath).normalize();
+        // Protect against directory attacks.
+        if (!normalizedResource.startsWith(prefixPath)) {
+            this.logger.warn("Direct access to skin file [{}] refused. Possible break-in attempt!", normalizedResource);
+
+            return null;
+        }
 
         URL url = classloader.getResource(templatePath);
 
         return url != null ? new ClassloaderTemplate(new ClassloaderResource(url, templateName)) : null;
     }

Vulnerable code:         // Prevent inclusion of templates from other directories
    private Template getClassloaderTemplate(String suffixPath, String templateName)
        return getClassloaderTemplate(Thread.currentThread().getContextClassLoader(), suffixPath, templateName);
    private Template getClassloaderTemplate(ClassLoader classloader, String suffixPath, String templateName)
        String templatePath = suffixPath + templateName;
Fixed code:         // Prevent access to resources from other directories
import java.nio.file.Path;
import java.nio.file.Paths;
    private Template getClassloaderTemplate(String prefixPath, String templateName)
        return getClassloaderTemplate(Thread.currentThread().getContextClassLoader(), prefixPath, templateName);
    private Template getClassloaderTemplate(ClassLoader classloader, String prefixPath, String templateName)
        String templatePath = prefixPath + templateName;

        // Prevent access to resources from other directories
        Path normalizedResource = Paths.get(templatePath).normalize();
        // Protect against directory attacks.
        if (!normalizedResource.startsWith(prefixPath)) {
            this.logger.warn("Direct access to skin file [{}] refused. Possible break-in attempt!", normalizedResource);

            return null;
        }