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

jbonofre pushed a commit to branch karaf-4.4.x
in repository https://gitbox.apache.org/repos/asf/karaf.git


The following commit(s) were added to refs/heads/karaf-4.4.x by this push:
     new 175dbb23f1 [4.4.x] Restrict WebConsole plugin getResource() to /res/ 
paths (#2910)
175dbb23f1 is described below

commit 175dbb23f1e44451c6a70c24b2e1283389352b98
Author: JB Onofré <[email protected]>
AuthorDate: Thu Sep 17 09:37:55 2026 +0200

    [4.4.x] Restrict WebConsole plugin getResource() to /res/ paths (#2910)
    
    * Restrict WebConsole plugin getResource() to /res/ paths (backport of 
#2892)
    
    FeaturesPlugin, GogoPlugin, HttpPlugin and InstancePlugin override
    getResource() and hand the request path directly to
    classLoader.getResource() with no restriction. Felix's own
    AbstractServlet.getResource() only spools paths whose remainder starts
    with /res/; these four Karaf plugins dropped that check. Restores the
    /res/ prefix check so only each plugin's own bundled static resources
    can be served through this path.
    
    * Normalize path and fix dead null-check in WebConsole getResource()
    
    Backport of the follow-up fix from #2892 to this 4.4.x branch's
    getResource() implementations (javax.servlet / AbstractWebConsolePlugin).
    
    FeaturesPlugin, GogoPlugin, HttpPlugin and InstancePlugin checked
    `path == null` after already calling `path.substring(...)` on it,
    so the check could never run (a null path throws NPE from substring
    first). Move the null guard before the substring call so it is
    actually effective (GogoPlugin, FeaturesPlugin, InstancePlugin had
    this dead check; HttpPlugin gets the same guard for consistency).
    
    Also close a residual path-traversal gap in the /res/ prefix check:
    a request like /res/../forbidden_directory/secret could still satisfy
    `startsWith("/res/")` before being handed to classLoader.getResource().
    Normalize the path with URI.normalize() before the prefix check, so
    ".." segments are resolved first and traversal attempts no longer
    start with /res/ after normalization; a path that fails to parse as
    a URI is rejected rather than passed through.
    
    Addresses PR review feedback from CptBartender and holgerfriedrich on #2892.
    
    Co-Authored-By: Claude Sonnet 5 <[email protected]>
    
    ---------
    
    Co-authored-by: Claude Sonnet 5 <[email protected]>
---
 .../org/apache/karaf/webconsole/features/FeaturesPlugin.java  | 10 +++++++++-
 .../java/org/apache/karaf/webconsole/gogo/GogoPlugin.java     | 11 ++++++++++-
 .../java/org/apache/karaf/webconsole/http/HttpPlugin.java     | 11 ++++++++++-
 .../org/apache/karaf/webconsole/instance/InstancePlugin.java  | 11 ++++++++++-
 4 files changed, 39 insertions(+), 4 deletions(-)

diff --git 
a/webconsole/features/src/main/java/org/apache/karaf/webconsole/features/FeaturesPlugin.java
 
b/webconsole/features/src/main/java/org/apache/karaf/webconsole/features/FeaturesPlugin.java
index 0ad1fef12e..9804b9fc4f 100644
--- 
a/webconsole/features/src/main/java/org/apache/karaf/webconsole/features/FeaturesPlugin.java
+++ 
b/webconsole/features/src/main/java/org/apache/karaf/webconsole/features/FeaturesPlugin.java
@@ -147,8 +147,16 @@ public class FeaturesPlugin extends 
AbstractWebConsolePlugin {
     }
 
     protected URL getResource(String path) {
+        if (path == null) {
+            return null;
+        }
         path = path.substring(NAME.length() + 1);
-        if (path == null || path.isEmpty()) {
+        try {
+            path = URI.create(path).normalize().getPath();
+        } catch (IllegalArgumentException e) {
+            return null;
+        }
+        if (path.isEmpty() || !path.startsWith("/res/")) {
             return null;
         }
         URL url = this.classLoader.getResource(path);
diff --git 
a/webconsole/gogo/src/main/java/org/apache/karaf/webconsole/gogo/GogoPlugin.java
 
b/webconsole/gogo/src/main/java/org/apache/karaf/webconsole/gogo/GogoPlugin.java
index 5e8aaf29e7..4be8142e6d 100644
--- 
a/webconsole/gogo/src/main/java/org/apache/karaf/webconsole/gogo/GogoPlugin.java
+++ 
b/webconsole/gogo/src/main/java/org/apache/karaf/webconsole/gogo/GogoPlugin.java
@@ -31,6 +31,7 @@ import java.io.PipedInputStream;
 import java.io.PipedOutputStream;
 import java.io.PrintStream;
 import java.io.PrintWriter;
+import java.net.URI;
 import java.net.URL;
 import java.security.AccessControlContext;
 import java.security.AccessController;
@@ -111,8 +112,16 @@ public class GogoPlugin extends AbstractWebConsolePlugin {
     }
 
     protected URL getResource(String path) {
+        if (path == null) {
+            return null;
+        }
         path = path.substring(NAME.length() + 1);
-        if (path == null || path.isEmpty()) {
+        try {
+            path = URI.create(path).normalize().getPath();
+        } catch (IllegalArgumentException e) {
+            return null;
+        }
+        if (path.isEmpty() || !path.startsWith("/res/")) {
             return null;
         }
         URL url = this.getClass().getClassLoader().getResource(path);
diff --git 
a/webconsole/http/src/main/java/org/apache/karaf/webconsole/http/HttpPlugin.java
 
b/webconsole/http/src/main/java/org/apache/karaf/webconsole/http/HttpPlugin.java
index 7156302473..554910094b 100644
--- 
a/webconsole/http/src/main/java/org/apache/karaf/webconsole/http/HttpPlugin.java
+++ 
b/webconsole/http/src/main/java/org/apache/karaf/webconsole/http/HttpPlugin.java
@@ -19,6 +19,7 @@ package org.apache.karaf.webconsole.http;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintWriter;
+import java.net.URI;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -113,8 +114,16 @@ public class HttpPlugin extends AbstractWebConsolePlugin {
     }
 
     protected URL getResource(String path) {
+        if (path == null) {
+            return null;
+        }
         path = path.substring(NAME.length() + 1);
-        if (path.isEmpty()) {
+        try {
+            path = URI.create(path).normalize().getPath();
+        } catch (IllegalArgumentException e) {
+            return null;
+        }
+        if (path.isEmpty() || !path.startsWith("/res/")) {
             return null;
         }
         URL url = this.classLoader.getResource(path);
diff --git 
a/webconsole/instance/src/main/java/org/apache/karaf/webconsole/instance/InstancePlugin.java
 
b/webconsole/instance/src/main/java/org/apache/karaf/webconsole/instance/InstancePlugin.java
index a45a23f768..85fa97dee0 100644
--- 
a/webconsole/instance/src/main/java/org/apache/karaf/webconsole/instance/InstancePlugin.java
+++ 
b/webconsole/instance/src/main/java/org/apache/karaf/webconsole/instance/InstancePlugin.java
@@ -19,6 +19,7 @@ package org.apache.karaf.webconsole.instance;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintWriter;
+import java.net.URI;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.List;
@@ -172,8 +173,16 @@ public class InstancePlugin extends 
AbstractWebConsolePlugin {
     }
 
     protected URL getResource(String path) {
+        if (path == null) {
+            return null;
+        }
         path = path.substring(NAME.length() + 1);
-        if (path == null || path.isEmpty()) {
+        try {
+            path = URI.create(path).normalize().getPath();
+        } catch (IllegalArgumentException e) {
+            return null;
+        }
+        if (path.isEmpty() || !path.startsWith("/res/")) {
             return null;
         }
         URL url = this.classLoader.getResource(path);

Reply via email to