Jenkins Open Redirect 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
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]
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/"));+}+}+}+}