[SECURITY-4016]

This commit is contained in:
Daniel Beck authored and Jenkins CERT CI committed 2026-08-25 11:14:59 +00:00
1 parent 650576ea8d
commit 69b9bdc141
2 files changed
+52 -1

No files matched your search

@@ -678,7 +678,9 @@ public abstract class SecurityRealm implements Describable<SecurityRealm>, Exten
apf.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/loginError"));
filters.add(apf);
}
filters.add(new RememberMeAuthenticationFilter(sc.manager2, sc.rememberMe2));
final RememberMeAuthenticationFilter rememberMe = new RememberMeAuthenticationFilter(sc.manager2, sc.rememberMe2);
rememberMe.setSessionAuthenticationStrategy(new SessionFixationProtectionStrategy());
filters.add(rememberMe);
filters.addAll(commonFilters());
return new ChainedServletFilter2(filters);
}
@@ -37,9 +37,11 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.MockAuthorizationStrategy;
import org.jvnet.hudson.test.junit.jupiter.WithJenkins;
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;
/**
* Split from {@link SecurityRealmTest} because this is parameterized.
@@ -114,4 +116,51 @@ class SecurityRealmSecurity2371Test {
System.clearProperty(SecurityRealm.class.getName() + ".sessionFixationProtectionMode");
}
}
@Issue("SECURITY-4016")
@ParameterizedTest
@MethodSource("modes")
void testSessionChangeOnRememberMeAutoLogin(Integer mode) throws Exception {
if (mode != null) {
System.setProperty(SecurityRealm.class.getName() + ".sessionFixationProtectionMode", String.valueOf(mode));
}
try {
HudsonPrivateSecurityRealm realm = new HudsonPrivateSecurityRealm(false, false, null);
j.jenkins.setSecurityRealm(realm);
j.jenkins.setDisableRememberMe(false);
j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy()
.grant(Jenkins.READ).everywhere().toEveryone()
.grant(Jenkins.ADMINISTER).everywhere().to(USERNAME));
realm.createAccount(USERNAME, USERNAME);
// Phase 1: attacker prepares an anonymous session
JenkinsRule.WebClient attacker = j.createWebClient();
attacker.goTo(""); // establishes anonymous session
Cookie attackerSession = attacker.getCookieManager().getCookie(SESSION_COOKIE_NAME);
// Phase 2: victim logs in with remember-me
JenkinsRule.WebClient victim = j.createWebClient();
victim.login(USERNAME, USERNAME, true);
Cookie rememberMeCookie = victim.getCookieManager()
.getCookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY);
// Phase 3: victim's session-only cookie disappears (browser restart)
// but remember-me persists. Attacker plants their session ID.
JenkinsRule.WebClient browser = j.createWebClient();
browser.getCookieManager().addCookie(attackerSession); // planted by sibling page
browser.getCookieManager().addCookie(rememberMeCookie); // survives browser restart
// Phase 4: victim navigates to Jenkins — remember-me auto-login fires
browser.goTo("");
Cookie postLoginSession = browser.getCookieManager().getCookie(SESSION_COOKIE_NAME);
// The session must have been rotated
assertNotEquals(attackerSession.getValue(), postLoginSession.getValue());
// Phase 5: replaying the pre-authentication session must not grant access
assertThrows(FailingHttpStatusCodeException.class, () -> attacker.goTo("manage"));
} finally {
System.clearProperty(SecurityRealm.class.getName() + ".sessionFixationProtectionMode");
}
}
}