From ad486d3f6e977755eaae8e75bc645779b82e2a03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Csahvx655-wq=E2=80=9D?= <“sahvx655@gmail.com”> Date: Wed, 3 Jun 2026 13:09:34 +0530 Subject: [PATCH] Prevent URL path traversal bypass via percent encoding in UrlValidator --- .../validator/routines/UrlValidator.java | 28 ++++++++++++++++++- .../validator/routines/UrlValidatorTest.java | 13 +++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/apache/commons/validator/routines/UrlValidator.java b/src/main/java/org/apache/commons/validator/routines/UrlValidator.java index 44862bcc9..3ae6e82b9 100644 --- a/src/main/java/org/apache/commons/validator/routines/UrlValidator.java +++ b/src/main/java/org/apache/commons/validator/routines/UrlValidator.java @@ -474,6 +474,32 @@ protected boolean isValidFragment(final String fragment) { return isOff(NO_FRAGMENTS); } + private String decodePath(final String path) { + final StringBuilder sb = new StringBuilder(); + final int len = path.length(); + for (int i = 0; i < len; i++) { + final char c = path.charAt(i); + if (c == '%' && i + 2 < len) { + final char h1 = path.charAt(i + 1); + final char h2 = path.charAt(i + 2); + if (h1 == '2') { + if (h2 == 'e' || h2 == 'E') { + sb.append('.'); + i += 2; + continue; + } + if (h2 == 'f' || h2 == 'F') { + sb.append('/'); + i += 2; + continue; + } + } + } + sb.append(c); + } + return sb.toString(); + } + /** * Returns true if the path is valid. A {@code null} value is considered invalid. * @@ -487,7 +513,7 @@ protected boolean isValidPath(final String path) { try { // Don't omit host otherwise leading path may be taken as host if it starts with // - final URI uri = new URI(null, "localhost", path, null); + final URI uri = new URI(null, "localhost", decodePath(path), null); final String norm = uri.normalize().getPath(); if (norm.startsWith("/../") // Trying to go via the parent dir || norm.equals("/..")) { // Trying to go to the parent dir diff --git a/src/test/java/org/apache/commons/validator/routines/UrlValidatorTest.java b/src/test/java/org/apache/commons/validator/routines/UrlValidatorTest.java index 25e0b5dc5..7708f8842 100644 --- a/src/test/java/org/apache/commons/validator/routines/UrlValidatorTest.java +++ b/src/test/java/org/apache/commons/validator/routines/UrlValidatorTest.java @@ -524,6 +524,19 @@ void testValidator363() { assertTrue(urlValidator.isValid("http://www.example.org/.../..")); } + @Test + void testPathTraversalPercentEncoding() { + final UrlValidator urlValidator = new UrlValidator(); + assertFalse(urlValidator.isValid("http://www.example.org/..%2fworld")); + assertFalse(urlValidator.isValid("http://www.example.org/..%2Fworld")); + assertFalse(urlValidator.isValid("http://www.example.org/%2e%2e/world")); + assertFalse(urlValidator.isValid("http://www.example.org/%2e%2e%2fworld")); + assertFalse(urlValidator.isValid("http://www.example.org/%2E%2E%2Fworld")); + assertFalse(urlValidator.isValid("http://www.example.org/..%2f")); + assertFalse(urlValidator.isValid("http://www.example.org/%2e%2e")); + assertFalse(urlValidator.isValid("http://www.example.org/%2e%2e/")); + } + @Test void testValidator375() { final UrlValidator validator = new UrlValidator();