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

pdallig pushed a commit to branch branch-0.12
in repository https://gitbox.apache.org/repos/asf/zeppelin.git


The following commit(s) were added to refs/heads/branch-0.12 by this push:
     new d3bbd11295 [ZEPPELIN-6190] Prevent directory escape bypass through 
repeated URL decoding
d3bbd11295 is described below

commit d3bbd11295e75590fbcfcbf256c7acb78ee5f266
Author: ChanHo Lee <chanho0...@gmail.com>
AuthorDate: Tue Jun 3 22:13:59 2025 +0900

    [ZEPPELIN-6190] Prevent directory escape bypass through repeated URL 
decoding
    
    ### What is this PR for?
    This PR addresses an issue in `NotebookService` where the notebook path 
validation only performs a single decoding pass.
    This allowed a malicious user to bypass validation by double-encoding the 
`".."` token.
    By implementing the repeated decoding, we can prevent this bypass.
    Additionally, to prevent excessive decoding attempts, a maximum limit on 
the number of decoding attempts has been added.
    
    ### What type of PR is it?
    Hot Fix
    
    ### What is the Jira issue?
    https://issues.apache.org/jira/projects/ZEPPELIN/issues/ZEPPELIN-6190
    
    ### How should this be tested?
    * CI
    
    ### Questions:
    * Does the license files need to update? No
    * Is there breaking changes for older versions?
      * There may be minor compatibility issues if a user relies on multiple 
encoded paths, but this is unlikely in realistic scenarios.
    * Does this needs documentation? No
    
    
    Closes #4891 from tbonelee/fix-validating-note-path.
    
    Signed-off-by: Philipp Dallig <philipp.dal...@gmail.com>
---
 .../org/apache/zeppelin/service/NotebookService.java | 20 +++++++++++++++++++-
 .../apache/zeppelin/service/NotebookServiceTest.java | 13 +++++++++++++
 2 files changed, 32 insertions(+), 1 deletion(-)

diff --git 
a/zeppelin-server/src/main/java/org/apache/zeppelin/service/NotebookService.java
 
b/zeppelin-server/src/main/java/org/apache/zeppelin/service/NotebookService.java
index c924ed898b..3947f4f186 100644
--- 
a/zeppelin-server/src/main/java/org/apache/zeppelin/service/NotebookService.java
+++ 
b/zeppelin-server/src/main/java/org/apache/zeppelin/service/NotebookService.java
@@ -24,6 +24,7 @@ import static 
org.apache.zeppelin.interpreter.InterpreterResult.Code.ERROR;
 import static org.apache.zeppelin.scheduler.Job.Status.ABORT;
 
 import java.io.IOException;
+import java.io.UnsupportedEncodingException;
 import java.net.URLDecoder;
 import java.nio.charset.StandardCharsets;
 import java.text.ParseException;
@@ -239,7 +240,7 @@ public class NotebookService {
 
     notePath = notePath.replace("\r", " ").replace("\n", " ");
 
-    notePath = URLDecoder.decode(notePath, StandardCharsets.UTF_8.toString());
+    notePath = decodeRepeatedly(notePath);
     if (notePath.endsWith("/")) {
       throw new IOException("Note name shouldn't end with '/'");
     }
@@ -1563,4 +1564,21 @@ public class NotebookService {
       return false;
     }
   }
+
+  private static String decodeRepeatedly(final String encoded) throws 
IOException {
+    String previous = encoded;
+    int maxDecodeAttempts = 5;
+    int attempts = 0;
+
+    while (attempts < maxDecodeAttempts) {
+      String decoded = URLDecoder.decode(previous, StandardCharsets.UTF_8);
+      attempts++;
+      if (decoded.equals(previous)) {
+        return decoded;
+      }
+      previous = decoded;
+    }
+
+    throw new IOException("Exceeded maximum decode attempts. Possible 
malicious input.");
+  }
 }
diff --git 
a/zeppelin-server/src/test/java/org/apache/zeppelin/service/NotebookServiceTest.java
 
b/zeppelin-server/src/test/java/org/apache/zeppelin/service/NotebookServiceTest.java
index be30a3cda8..152d085668 100644
--- 
a/zeppelin-server/src/test/java/org/apache/zeppelin/service/NotebookServiceTest.java
+++ 
b/zeppelin-server/src/test/java/org/apache/zeppelin/service/NotebookServiceTest.java
@@ -615,6 +615,19 @@ class NotebookServiceTest {
     } catch (IOException e) {
       assertEquals("Note name can not contain '..'", e.getMessage());
     }
+    try {
+      // Double URL encoding of ".."
+      notebookService.normalizeNotePath("%252e%252e/%252e%252e/tmp/test333");
+      fail("Should fail");
+    } catch (IOException e) {
+      assertEquals("Note name can not contain '..'", e.getMessage());
+    }
+    try {
+      notebookService.normalizeNotePath("%252525252e%252525252e/tmp/test444");
+      fail("Should fail");
+    } catch (IOException e) {
+      assertEquals("Exceeded maximum decode attempts. Possible malicious 
input.", e.getMessage());
+    }
     try {
       notebookService.normalizeNotePath("./");
       fail("Should fail");

Reply via email to