You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zeppelin.apache.org by pd...@apache.org on 2025/06/03 13:14:05 UTC
(zeppelin) branch master updated: [ZEPPELIN-6190] Prevent directory escape bypass through repeated URL decoding
This is an automated email from the ASF dual-hosted git repository.
pdallig pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zeppelin.git
The following commit(s) were added to refs/heads/master by this push:
new 588b54bf54 [ZEPPELIN-6190] Prevent directory escape bypass through repeated URL decoding
588b54bf54 is described below
commit 588b54bf54cbbf845af5ca0c9369ae4a74ec3ddf
Author: ChanHo Lee <ch...@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 <ph...@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");