You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by pz...@apache.org on 2026/10/12 15:29:03 UTC
(knox) branch master updated: KNOX-3510 - Normalize decoded dispatch URLs and reject path traversal (#1449)
This is an automated email from the ASF dual-hosted git repository.
pzampino pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git
The following commit(s) were added to refs/heads/master by this push:
new 8a55ebe1e KNOX-3510 - Normalize decoded dispatch URLs and reject path traversal (#1449)
8a55ebe1e is described below
commit 8a55ebe1ef5654d708871608c89a3bf39c482f23
Author: Phil Zampino <pz...@apache.org>
AuthorDate: Mon Oct 12 11:28:57 2026 -0400
KNOX-3510 - Normalize decoded dispatch URLs and reject path traversal (#1449)
Co-authored-by: Claude Code <no...@anthropic.com>
---
.../knox/gateway/dispatch/URLDecodingDispatch.java | 19 +++++++++++++-
.../gateway/dispatch/URLDecodingDispatchTest.java | 30 ++++++++++++++++++++++
2 files changed, 48 insertions(+), 1 deletion(-)
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/URLDecodingDispatch.java b/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/URLDecodingDispatch.java
index 34c8f41f7..dd6befb0e 100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/URLDecodingDispatch.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/URLDecodingDispatch.java
@@ -52,6 +52,23 @@ public class URLDecodingDispatch extends ConfigurableDispatch {
str.append('?');
str.append(query);
}
- return URI.create(str.toString());
+ return normalize(URI.create(str.toString()));
+ }
+
+ /**
+ * Decoding can turn encoded dot segments (e.g. %2e%2e%2f) into real ones,
+ * so resolve them and refuse any path that still climbs above its root.
+ */
+ private static URI normalize(final URI uri) {
+ final URI normalized = uri.normalize();
+ final String path = normalized.getPath();
+ if ( path != null ) {
+ for ( final String segment : path.split("/") ) {
+ if ( "..".equals(segment) ) {
+ throw new IllegalArgumentException("Path traversal is not allowed in the dispatch URL");
+ }
+ }
+ }
+ return normalized;
}
}
diff --git a/gateway-spi/src/test/java/org/apache/knox/gateway/dispatch/URLDecodingDispatchTest.java b/gateway-spi/src/test/java/org/apache/knox/gateway/dispatch/URLDecodingDispatchTest.java
index c04b6eff2..db6f800da 100644
--- a/gateway-spi/src/test/java/org/apache/knox/gateway/dispatch/URLDecodingDispatchTest.java
+++ b/gateway-spi/src/test/java/org/apache/knox/gateway/dispatch/URLDecodingDispatchTest.java
@@ -58,4 +58,34 @@ public class URLDecodingDispatchTest {
assertThat(uri.toASCIIString(),
is("https://localhost:8443/gateway/sandbox/datanode/datanode.html?host=http://localhost:9864"));
}
+
+ private URI dispatchUrlFor(final String url) {
+ request = EasyMock.createNiceMock(HttpServletRequest.class);
+ EasyMock.expect(request.getRequestURL()).andReturn(new StringBuffer(url)).anyTimes();
+ EasyMock.expect(request.getQueryString()).andReturn(null).anyTimes();
+ EasyMock.replay(request);
+ return dispatch.getDispatchUrl(request);
+ }
+
+ @Test(timeout = TestUtils.MEDIUM_TIMEOUT)
+ public void testEncodedDotSegmentsAreNormalized() {
+ URI uri = dispatchUrlFor("https://localhost:8443/gateway/sandbox/datanode/a/b/%2e%2e/c.html");
+ assertThat(uri.toASCIIString(), is("https://localhost:8443/gateway/sandbox/datanode/a/c.html"));
+
+ uri = dispatchUrlFor("https://localhost:8443/gateway/sandbox/datanode/a/%2e%2e%2fc.html");
+ assertThat(uri.toASCIIString(), is("https://localhost:8443/gateway/sandbox/datanode/c.html"));
+
+ uri = dispatchUrlFor("https://localhost:8443/gateway/sandbox/datanode/./a.html");
+ assertThat(uri.toASCIIString(), is("https://localhost:8443/gateway/sandbox/datanode/a.html"));
+ }
+
+ @Test(timeout = TestUtils.MEDIUM_TIMEOUT, expected = IllegalArgumentException.class)
+ public void testEncodedTraversalAboveRootIsRejected() {
+ dispatchUrlFor("https://localhost:8443/%2e%2e/%2e%2e/etc/passwd");
+ }
+
+ @Test(timeout = TestUtils.MEDIUM_TIMEOUT, expected = IllegalArgumentException.class)
+ public void testTraversalAboveRootIsRejected() {
+ dispatchUrlFor("https://localhost:8443/../etc/passwd");
+ }
}