Security context
Medium· 4.3GHSA-8hmv-92wm-39ch CVE-2025-27625CWE-601Published Mar 6, 2025

Jenkins Open Redirect vulnerability

Research this vulnerability

Research is free — Hunters explains how the bug works, the root-cause code pattern, how the fix addresses it, and how to test whether a target is affected, in chat. Investigate & write exploit is a paid run — the engine reads the advisory and fix commits, then builds and validates a working proof-of-concept exploit with reproduction steps.

Affected versions

0 → fixed in 2.492.22.493 → fixed in 2.500

Details

Various features in Jenkins redirect users to partially user-controlled URLs inside Jenkins. To prevent open redirect vulnerabilities, Jenkins limits redirections to safe URLs (neither absolute nor scheme-relative/network-path reference). In Jenkins 2.499 and earlier, LTS 2.492.1 and earlier, redirects starting with backslash (`\`) characters are considered safe. This allows attackers to perform phishing attacks by having users go to a Jenkins URL that will forward them to a different site, because browsers interpret these characters as part of scheme-relative redirects. Jenkins 2.500, LTS 2.492.2 considers redirects to URLs starting with backslash (`\`) characters to be unsafe, rejecting such redirects.

The fix

[SECURITY-3501]

Daniel Beck· Feb 25, 2025, 04:17 PM+3924a9a3ecd08
core/src/main/java/hudson/Util.java+1 1
@@ -1651,7 +1651,7 @@ public static boolean isAbsoluteUri(@NonNull String uri) {
* @since 2.3 / 1.651.2
*/
public static boolean isSafeToRedirectTo(@NonNull String uri) {
- return !isAbsoluteUri(uri) && !uri.startsWith("//");
+ return !isAbsoluteUri(uri) && !uri.startsWith("\\") && !uri.replace('\\', '/').startsWith("//");
}
/**
core/src/test/java/hudson/UtilTest.java+5 1
@@ -416,12 +416,16 @@ public void testIsAbsoluteUri() {
}
@Test
- @Issue("SECURITY-276")
+ @Issue({"SECURITY-276", "SECURITY-3501"})
public void testIsSafeToRedirectTo() {
assertFalse(Util.isSafeToRedirectTo("http://foobar/"));
assertFalse(Util.isSafeToRedirectTo("mailto:kk@kohsuke.org"));
assertFalse(Util.isSafeToRedirectTo("d123://test/"));
assertFalse(Util.isSafeToRedirectTo("//google.com"));
+ assertFalse(Util.isSafeToRedirectTo("\\\\google.com"));
+ assertFalse(Util.isSafeToRedirectTo("\\/google.com"));
+ assertFalse(Util.isSafeToRedirectTo("/\\google.com"));
+ assertFalse(Util.isSafeToRedirectTo("\\google.com"));
assertTrue(Util.isSafeToRedirectTo("foo/bar/abc:def"));
assertTrue(Util.isSafeToRedirectTo("foo?abc:def"));
test/src/test/java/jenkins/security/Security3501Test.java+33 0
@@ -0,0 +1,33 @@
+package jenkins.security;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+import java.util.List;
+import org.htmlunit.FailingHttpStatusCodeException;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.jvnet.hudson.test.JenkinsRule;
+import org.jvnet.hudson.test.RealJenkinsRule;
+
+public class Security3501Test {
+ @Rule
+ public RealJenkinsRule jj = new RealJenkinsRule();
+
+ @Test
+ public void testRedirects() throws Throwable {
+ jj.then(Security3501Test::_testRedirects);
+ }
+
+ public static void _testRedirects(JenkinsRule j) throws Exception {
+ List<String> prohibitedPaths = List.of("%5C%5Cexample.org", "%5C/example.org", "/%5Cexample.org", "//example.org", "https://example.org", "\\example.org");
+ for (String path : prohibitedPaths) {
+ try (JenkinsRule.WebClient wc = j.createWebClient().withRedirectEnabled(false)) {
+ final FailingHttpStatusCodeException fhsce = Assert.assertThrows(FailingHttpStatusCodeException.class, () -> wc.goTo("userContent?path=" + path));
+ assertThat(fhsce.getStatusCode(), is(302));
+ assertThat(fhsce.getResponse().getResponseHeaderValue("Location"), is(j.getURL().toExternalForm() + "userContent/"));
+ }
+ }
+ }
+}

References